<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>JVM 中篇-字节码与类的加载篇 | 无人深空</title><meta name="keywords" content="JVM"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="JVM 中篇-字节码与类的加载篇1.Class 文件结构1.Class 字节码文件结构                                           类型              名称              说明              长度              数量                              魔数             u4">
<meta property="og:type" content="article">
<meta property="og:title" content="JVM 中篇-字节码与类的加载篇">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/10/08/JVM%20%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="JVM 中篇-字节码与类的加载篇1.Class 文件结构1.Class 字节码文件结构                                           类型              名称              说明              长度              数量                              魔数             u4">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg">
<meta property="article:published_time" content="2022-10-08T01:00:38.000Z">
<meta property="article:modified_time" content="2022-10-10T07:35:05.921Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="JVM">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/10/08/JVM%20%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'JVM 中篇-字节码与类的加载篇',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-10-10 15:35:05'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">JVM 中篇-字节码与类的加载篇</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-10-08T01:00:38.000Z" title="发表于 2022-10-08 09:00:38">2022-10-08</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-10-10T07:35:05.921Z" title="更新于 2022-10-10 15:35:05">2022-10-10</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java/">Java</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">29.9k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>98分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="JVM 中篇-字节码与类的加载篇"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="JVM-中篇-字节码与类的加载篇"><a href="#JVM-中篇-字节码与类的加载篇" class="headerlink" title="JVM 中篇-字节码与类的加载篇"></a><a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=204">JVM 中篇-字节码与类的加载篇</a></h2><h3 id="1-Class-文件结构"><a href="#1-Class-文件结构" class="headerlink" title="1.Class 文件结构"></a>1.Class 文件结构</h3><h4 id="1-Class-字节码文件结构"><a href="#1-Class-字节码文件结构" class="headerlink" title="1.Class 字节码文件结构"></a>1.Class 字节码文件结构</h4><table>
    <tbody>  
        <tr>
            <th></th> 
            <th>类型</th> 
            <th>名称</th> 
            <th>说明</th> 
            <th>长度</th> 
            <th>数量</th> 
       </tr>
       <tr>
            <td>魔数</td>
            <td>u4</td>
            <td>magic</td>
            <td>魔数,识别Class文件格式</td>
            <td>4个字节</td>     
            <td>1</td>
       </tr>
       <tr>
            <td rowspan="2">版本号</td>
            <td>u2</td>
            <td>minor_version</td>
            <td>副版本号(小版本)</td>
            <td>2个字节</td>     
            <td>1</td>
       </tr>
       <tr>
            <td>u2</td>
            <td>major_version</td>
            <td>主版本号(大版本)</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>	
        <tr>
            <td rowspan="2">常量池集合</td>
            <td>u2</td>
            <td>constant_pool_count</td>
            <td>常量池计数器</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>cp_info</td>
            <td>constant_pool</td>
            <td>常量池表</td>
            <td>n个字节</td>     
            <td>constant_pool_count - 1</td>
        </tr>
        <tr>
            <td>访问标识</td>
            <td>u2</td>
            <td>access_flags</td>
            <td>访问标识</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>	
        <tr>
            <td rowspan="4">索引集合</td>
            <td>u2</td>
            <td>this_class</td>
            <td>类索引</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>u2</td>
            <td>super_class</td>
            <td>父类索引</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>u2</td>
            <td>interfaces_count</td>
            <td>接口计数器</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>u2</td>
            <td>interfaces</td>
            <td>接口索引集合</td>
            <td>2个字节</td>     
            <td>interfaces_count</td>
        </tr>    
        <tr>
            <td rowspan="2">字段表集合</td>
            <td>u2</td>
            <td>fields_count</td>
            <td>字段计数器</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>field_info</td>
            <td>fields</td>
            <td>字段表</td>
            <td>n个字节</td>     
            <td>fields_count</td>
        </tr>	
        <tr>
            <td rowspan="2">方法表集合</td>
            <td>u2</td>
            <td>methods_count</td>
            <td>方法计数器</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>method_info</td>
            <td>methods</td>
            <td>方法表</td>
            <td>n个字节</td>     
            <td>methods_count</td>
        </tr>
        <tr>
            <td rowspan="2">属性表集合</td>
            <td>u2</td>
            <td>attributes_count</td>
            <td>属性计数器</td>
            <td>2个字节</td>     
            <td>1</td>
        </tr>
        <tr>
            <td>attribute_info</td>
            <td>attributes</td>
            <td>属性表</td>
            <td>n个字节</td>     
            <td>attributes_count</td>
        </tr>	
   <tbody> 
</table>

<h4 id="2-Class-文件数据类型"><a href="#2-Class-文件数据类型" class="headerlink" title="2.Class 文件数据类型"></a>2.Class 文件数据类型</h4><table>
<thead>
<tr>
<th align="left">数据类型</th>
<th align="left">定义</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="left">无符号数</td>
<td align="left">无符号数可以用来描述数字、索引引用、数量值或按照 utf-8 编码构成的字符串值。</td>
<td align="left">其中无符号数属于基本的数据类型。 以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节</td>
</tr>
<tr>
<td align="left">表</td>
<td align="left">表是由多个无符号数或其他表构成的复合数据结构。</td>
<td align="left">所有的表都以“_info”结尾。 由于表没有固定长度，所以通常会在其前面加上个数说明。</td>
</tr>
</tbody></table>
<h4 id="3-魔数（Magic-Number）"><a href="#3-魔数（Magic-Number）" class="headerlink" title="3.魔数（Magic Number）"></a>3.魔数（Magic Number）</h4><ul>
<li><p>每个 Class 文件开头的 4 个字节的无符号整数称为魔数（Magic Number）</p>
</li>
<li><p>它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的 Class 文件。即：魔数是 Class 文件的标识符</p>
</li>
<li><p>魔数值固定为 <code>0xCAFEBABE</code>。不会改变</p>
</li>
<li><p>如果一个 Class 文件不以 0xCAFEBABE 开头，虚拟机在进行文件校验的时候就会直接抛出以下错误：</p>
<blockquote>
<p>Error: A JNI error has occurred, please check your installation and try again<br>Exception in thread “main” <code>java.lang.ClassFormatError</code>: Incompatible magic value 1885430635 in class file <code>StringTest</code></p>
</blockquote>
</li>
<li><p>使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑，因为文件扩展名可以随意地改动</p>
</li>
</ul>
<h4 id="4-文件版本号"><a href="#4-文件版本号" class="headerlink" title="4.文件版本号"></a>4.文件版本号</h4><blockquote>
<p>紧接着魔数的 4 个字节存储的是 Class 文件的版本号。同样也是 4 个字节。第 5 个和第 6 个字节所代表的含义就是编译的副版本号 minor_version，而第 7 个和第 8 个字节就是编译的主版本号 major_version</p>
<p>它们共同构成了 class 文件的格式版本号。譬如某个 Class 文件的主版本号为 M，副版本号为 m，那么这个 Class 文件的格式版本号就确定为 M.m</p>
<p>版本号和 Java 编译器的对应关系如下表：</p>
<table>
<thead>
<tr>
<th>主版本（十进制）</th>
<th>副版本（十进制）</th>
<th>编译器版本</th>
</tr>
</thead>
<tbody><tr>
<td>45</td>
<td>3</td>
<td>1.1</td>
</tr>
<tr>
<td>46</td>
<td>0</td>
<td>1.2</td>
</tr>
<tr>
<td>47</td>
<td>0</td>
<td>1.3</td>
</tr>
<tr>
<td>48</td>
<td>0</td>
<td>1.4</td>
</tr>
<tr>
<td>49</td>
<td>0</td>
<td>1.5</td>
</tr>
<tr>
<td>50</td>
<td>0</td>
<td>1.6</td>
</tr>
<tr>
<td>51</td>
<td>0</td>
<td>1.7</td>
</tr>
<tr>
<td>52</td>
<td>0</td>
<td>1.8</td>
</tr>
<tr>
<td>53</td>
<td>0</td>
<td>1.9</td>
</tr>
<tr>
<td>54</td>
<td>0</td>
<td>1.10</td>
</tr>
<tr>
<td>55</td>
<td>0</td>
<td>1.11</td>
</tr>
</tbody></table>
<p>Java 的版本号是从 45 开始的，JDK1.1 之后的每个 JDK 大版本发布主版本号向上加 1</p>
<p><strong>不同版本的 Java 编译器编译的 Class 文件对应的版本是不一样的。目前，高版本的 Java 虚拟机可以执行由低版本编译器生成的 Class 文件，但是低版本的 Java 虚拟机不能执行由高版本编译器生成的 Class 文件。否则 JVM 会抛出 <code>java.lang.UnsupportedClassVersionError</code> 异常（向下兼容）</strong></p>
<p>在实际应用中，由于开发环境和生产环境的不同，可能会导致该问题的发生。因此，需要我们在开发时，特别注意开发编译的 JDK 版本和生产环境中的 JDK 版本是否一致</p>
<p><strong>虚拟机 JDK 版本为 1.k（k&gt;&#x3D;2）时，对应的 class 文件格式版本号的范围为 45.0 - 44+k.0（含两端）</strong></p>
</blockquote>
<h4 id="5-常量池集合"><a href="#5-常量池集合" class="headerlink" title="5.常量池集合"></a>5.常量池集合</h4><ol>
<li><p>常量池概述</p>
<blockquote>
<p>常量池是 Class 文件中内容最为丰富的区域之一</p>
<p>常量池对于 Class 文件中的字段和方法解析也有着至关重要的作用</p>
<p>随着 Java 虚拟机的不断发展，常量池的内容也日渐丰富。可以说，<strong>常量池是整个 Class 文件的基石</strong></p>
<p>在版本号之后，紧跟着的是常量池的数量，以及若干个常量池表项</p>
<p>常量池中常量的数量是不固定的，所以在常量池的入口需要放置一项 u2 类型的无符号数，代表常量池容量计数值（constant_pool_count）。<strong>与 Java 中语言习惯不一样的是，这个容量计数是从 1 而不是 0 开始的</strong></p>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">名称</th>
<th align="left">数量</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2（无符号数）</td>
<td align="left">constant_pool_count</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">cp_info（表）</td>
<td align="left">constant_pool</td>
<td align="left">constant_pool_count - 1</td>
</tr>
</tbody></table>
<p>由上表可见，Class 文件使用了一个前置的容量计数器（constant_pool_count）加若干个连续的数据项（constant_pool）的形式来描述常量池内容。<strong>这一系列连续常量池数据称为常量池集合</strong></p>
<p><strong>常量池表项</strong>中，用于存放编译时期生成的各种<strong>字面量</strong>和<strong>符号引用</strong>，这部分内容将在类加载后进入方法区的<strong>运行时常量池</strong>中存放</p>
</blockquote>
</li>
<li><p>常量池计数器（constant_pool_count）</p>
<ul>
<li><p>由于常量池的数量不固定，时长时短，所以需要放置两个字节来表示常量池容量计数值</p>
</li>
<li><p>常量池容量计数值（u2 类型）<strong>：从 1 开始，表示常量池中有多少项常量</strong></p>
<blockquote>
<p>即 constant_pool_count&#x3D;1 表示常量池中有 0 个常量项</p>
</blockquote>
</li>
<li><p>Demo 的值为：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E6%B1%A0%E8%AE%A1%E6%95%B0%E5%99%A8.png" alt="常量池计数器"></p>
<blockquote>
<p>其值为 0x0016，也就是 22</p>
<p>需要注意的是，这实际上只有 21 项常量。索引为范围是 1-21</p>
<p>通常我们写代码时都是从 0 开始的，但是这里的常量池却是从 1 开始，因为它把第 0 项常量空出来了。这是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达<code>不引用任何一个常量池项目</code> 的含义，这种情况可用索引值 0 来表示</p>
</blockquote>
</li>
</ul>
</li>
<li><p>常量池表</p>
<blockquote>
<p>constant_pool 是一种表结构，以 1 ~ <code>constant_pool_count - 1</code> 为索引。表明了后面有多少个常量项</p>
<p>常量池主要存放两大类常量：<strong>字面量（Literal）</strong>和<strong>符号引用（Symbolic References）</strong></p>
<p>它包含了 class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量</p>
<p>常量池中的每一项都具备相同的特征：<strong>第 1 个字节作为类型标记，用于确定该项的格式，这个字节称为 tag byte（标记字节、标签字节）</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">标志(或标识)</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">CONSTANT_Utf8_info</td>
<td align="left">1</td>
<td align="left">UTF-8 编码的字符串</td>
</tr>
<tr>
<td align="left">CONSTANT_Integer_info</td>
<td align="left">3</td>
<td align="left">整型字面量</td>
</tr>
<tr>
<td align="left">CONSTANT_Float_info</td>
<td align="left">4</td>
<td align="left">浮点型字面量</td>
</tr>
<tr>
<td align="left">CONSTANT_Long_info</td>
<td align="left">5</td>
<td align="left">长整型字面量</td>
</tr>
<tr>
<td align="left">CONSTANT_Double_info</td>
<td align="left">6</td>
<td align="left">双精度浮点型字面量</td>
</tr>
<tr>
<td align="left">CONSTANT_Class_info</td>
<td align="left">7</td>
<td align="left">类或接口的符号引用</td>
</tr>
<tr>
<td align="left">CONSTANT_String_info</td>
<td align="left">8</td>
<td align="left">字符串类型字面量</td>
</tr>
<tr>
<td align="left">CONSTANT_Fieldref_info</td>
<td align="left">9</td>
<td align="left">字段的符号引用</td>
</tr>
<tr>
<td align="left">CONSTANT_Methodref_info</td>
<td align="left">10</td>
<td align="left">类中方法的符号引用</td>
</tr>
<tr>
<td align="left">CONSTANT_InterfaceMethodref_info</td>
<td align="left">11</td>
<td align="left">接口中方法的符号引用</td>
</tr>
<tr>
<td align="left">CONSTANT_NameAndType_info</td>
<td align="left">12</td>
<td align="left">字段或方法的符号引用</td>
</tr>
<tr>
<td align="left">CONSTANT_MethodHandle_info</td>
<td align="left">15</td>
<td align="left">表示方法句柄</td>
</tr>
<tr>
<td align="left">CONSTANT_MethodType_info</td>
<td align="left">16</td>
<td align="left">标志方法类型</td>
</tr>
<tr>
<td align="left">CONSTANT_InvokeDynamic_info</td>
<td align="left">18</td>
<td align="left">表示一个动态方法调用点</td>
</tr>
</tbody></table>
<ul>
<li><p>字面量和符号引用</p>
<blockquote>
<p>常量池主要存放两大类常量：<strong>字面量（Literal）</strong>和<strong>符号引用（Symbolic References）</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">常量</th>
<th align="left">具体的常量</th>
</tr>
</thead>
<tbody><tr>
<td align="left">字面量</td>
<td align="left">文本字符串</td>
</tr>
<tr>
<td align="left"></td>
<td align="left">声明为 final 的常量值</td>
</tr>
<tr>
<td align="left">符号引用</td>
<td align="left">类和接口的全限定名</td>
</tr>
<tr>
<td align="left"></td>
<td align="left">字段的名称和描述符</td>
</tr>
<tr>
<td align="left"></td>
<td align="left">方法的名称和描述符</td>
</tr>
</tbody></table>
<ul>
<li><p><strong>全限定名</strong></p>
<blockquote>
<p><code>com/atguigu/test/Demo</code> 这个就是类的全限定名，仅仅是把包名的 <code>.</code>替换成 <code>/</code>，为了使连续的多个全限定名之间不产生混淆，在使用时最后一般会加入一个<code>;</code>表示全限定名结束</p>
</blockquote>
</li>
<li><p><strong>简单名称</strong></p>
<blockquote>
<p>简单名称是指没有类型和参数修饰的方法或者字段名称</p>
<p>上面例子中的类的 <code>add()</code> 方法和 <code>num</code> 字段的简单名称分别是 <code>add</code> 和 <code>num</code></p>
</blockquote>
</li>
<li><p><strong>描述符</strong></p>
<blockquote>
<p><strong>描述符的作用是用来描述字段的数据类型、方法的参数列表（包括数量、类型以及顺序）和返回值</strong></p>
<p>根据描述符规则，基本数据类型（byte、char、double、float、int、long、short、boolean）以及代表无返回值的 void 类型都用一个大写字符来表示，而对象类型则用字符 L 加对象的全限定名来表示。详见下表：</p>
<table>
<thead>
<tr>
<th align="left">标志符</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">B</td>
<td align="left">基本数据类型 byte</td>
</tr>
<tr>
<td align="left">C</td>
<td align="left">基本数据类型 char</td>
</tr>
<tr>
<td align="left">D</td>
<td align="left">基本数据类型 double</td>
</tr>
<tr>
<td align="left">F</td>
<td align="left">基本数据类型 float</td>
</tr>
<tr>
<td align="left">I</td>
<td align="left">基本数据类型 int</td>
</tr>
<tr>
<td align="left">J</td>
<td align="left">基本数据类型 long</td>
</tr>
<tr>
<td align="left">S</td>
<td align="left">基本数据类型 short</td>
</tr>
<tr>
<td align="left">Z</td>
<td align="left">基本数据类型 boolean</td>
</tr>
<tr>
<td align="left">V</td>
<td align="left">代表 void 类型</td>
</tr>
<tr>
<td align="left">L</td>
<td align="left">对象类型，比如：<code>Ljava/lang/Object;</code></td>
</tr>
<tr>
<td align="left">[</td>
<td align="left">数组类型，代表一维数组。比如：&#96;double[] is [D</td>
</tr>
</tbody></table>
<p>用描述符来描述方法时，按照<strong>先参数列表，后返回值</strong>的顺序描述，参数列表按照参数的严格顺序放在一组小括号 <code>()</code> 之内</p>
<p>如方法 <code>java.lang.String tostring()</code> 的描述符为 <code>()Ljava/lang/String</code></p>
<p>方法 <code>int abc(int[]x, int y)</code> 的描述符为 <code>([II)I</code></p>
</blockquote>
</li>
</ul>
</li>
<li><p>补充说明</p>
<blockquote>
<p>虚拟机在加载 Class 文件时才会进行动态链接，即 Class 文件中不会保存各个方法和字段的最终内存布局信息</p>
<p>因此这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的</p>
<p><strong>当虚拟机运行时，需要从常量池中获得对应的符号引用，再在类加载过程中的解析阶段将其替换为直接引用，并翻译到具体的内存地址中</strong></p>
<p>符号引用和直接引用的区别与关联：</p>
</blockquote>
<ul>
<li><p>符号引用：符号引用以<strong>一组符号</strong>来描述所引用的目标，符号可以是任何形式的字面量，只要使用时能无歧义地定位到目标即可</p>
<blockquote>
<p><strong>符号引用与虚拟机实现的内存布局无关</strong>，引用的目标并不一定已经加载到了内存中</p>
</blockquote>
</li>
<li><p>直接引用：<strong>直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄</strong></p>
<blockquote>
<p><strong>直接引用是与虚拟机实现的内存布局相关的</strong>，同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不同。<strong>如果有了直接引用，那说明引用的目标必定已经存在于内存之中了</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>常量类型和结构</p>
<blockquote>
<p>常量池中每一项常量都是一个表，J0K1.7 之后共有 14 种不同的表结构数据。如下表格所示：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E7%B1%BB%E5%9E%8B%E5%92%8C%E7%BB%93%E6%9E%84.png" alt="常量类型和结构"></p>
<blockquote>
<p>根据上图每个类型的描述我们也可以知道每个类型是用来描述常量池中哪些内容（主要是字面量、符号引用）的</p>
<p>比如:</p>
<p><code>CONSTANT_Integer_info</code> 是用来描述常量池中字面量信息的，而且只是整型字面量信息</p>
<p>标志为 15、16、18 的常量项类型是用来支持动态语言调用的（jdk1.7 时才加入的）</p>
</blockquote>
</li>
<li><p>常量类型和结构细节说明</p>
<ul>
<li><p><code>CONSTANT_Class_info</code> 结构用于表示类或接口</p>
</li>
<li><p><code>CONSTAT_Fieldref_info</code>、<code>CONSTAHT_Methodref_info</code> 和 <code>CONSTANIT_InterfaceMethodref_info</code> 结构表示字段、类中方法和接口中方法</p>
</li>
<li><p><code>CONSTANT_String_info</code> 结构用于表示 String 类型的常量对象</p>
</li>
<li><p><code>CONSTANT_Integer_info</code> 和 <code>CONSTANT_Float_info</code> 表示 4 字节（int 和 float）的数值常量</p>
</li>
<li><p><code>CONSTANT_Long_info</code> 和 <code>CONSTAT_Double_info</code> 结构表示 8 字作（long 和 double）的数值常量</p>
<blockquote>
<p>在 class 文件的常最池表中，所行的 a 字节常借均占两个表成员（项）的空间</p>
<p>如果一个 <code>CONSTAHT_Long_info</code> 和 <code>CNSTAHT_Double_info</code> 结构在常量池中的索引位 n，则常量池中一个可用的索引位 n + 2，此时常量池长中索引为 n+1 的项仍然有效但必须视为不可用的</p>
</blockquote>
</li>
<li><p><code>CONSTANT_NameAndType_info</code> 结构用于表示字段或方法，但是和之前的 3 个结构不同</p>
<blockquote>
<p><code>CONSTANT_NameAndType_info</code> 结构没有指明该字段或方法所属的类或接口</p>
</blockquote>
</li>
<li><p><code>CONSTANT_Utf8_info</code> 用于表示字符常量的值</p>
</li>
<li><p><code>CONSTANT_MethodHandle_info</code> 结构用于表示方法句柄</p>
</li>
<li><p><code>CONSTANT_MethodType_info</code> 结构表示方法类型</p>
</li>
<li><p><code>CONSTANT_InvokeDynamic_info</code> 结构表示 <code>invokedynamic</code> 指令所用到的引导方法 <code>bootstrap method</code>、引导方法所用到的动态调用名称 <code>dynamic invocation name</code>、参数和返回类型，并可以给引导方法传入一系列称为静态参数 <code>static argument</code> 的常量</p>
</li>
</ul>
</li>
<li><p>常量类型和结构解析方法</p>
<ul>
<li>一个字节一个字节的解析</li>
<li>使用 <code>javap</code> 命令解析：<code>javap-verbose Demo.class</code> 或 <code>jclasslib</code> 工具会更方便</li>
</ul>
</li>
<li><p>总结1</p>
<ul>
<li><p>这 14 种表（或者常量项结构）的共同点是：表开始的第一位是一个 u1 类型的标志位（tag），代表当前这个常量项使用的是哪种表结构，即哪种常量类型</p>
</li>
<li><p>在常量池列表中，<code>CONSTANT_Utf8_info</code> 常量项是一种使用改进过的 UTF-8 编码格式来存储诸如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符串信息</p>
</li>
<li><p>这 14 种常量项结构还有一个特点是，其中 13 个常量项占用的字节固定，只有 <code>CONSTANT_Utf8_info</code> 占用字节不固定，其大小由 length 决定</p>
<blockquote>
<p>因为从常量池存放的内容可知，其存放的是字面量和符号引用，<strong>最终这些内容都会是一个字符串，这些字符串的大小是在编写程序时才确定</strong></p>
<p>比如定义一个类，类名可以取长取短，所以在没编译前，大小不固定，编译后，通过 utf-8 编码，就可以知道其长度</p>
</blockquote>
</li>
</ul>
</li>
<li><p>总结2</p>
<ul>
<li><p>常量池：可以理解为 Class 文件之中的资源仓库，<strong>它是 Class 文件结构中与其他项目关联最多的数据类型（后面的很多数据类型都会指向此处），也是占用 Class 文件空间最大的数据项目之一</strong></p>
</li>
<li><p>常量池中为什么要包含这些内容？Java 代码在进行 Javac 编译的时候，并不像 C 和 C++那样有<code>连接</code>这一步骤，而是在虚拟机加载 C1ass 文件的时候进行动态链接</p>
<blockquote>
<p>也就是说，<strong>在 Class 文件中不会保存各个方法、字段的最终内存布局信息，因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址，也就无法直接被虚拟机使用</strong></p>
<p>当虚拟机运行时，需要从常量池获得对应的符号引用，再在类创建时或运行时解析、翻译到具体的内存地址之中</p>
<p>关于类的创建和动态链接的内容，在虚拟机类加载过程时再进行详细讲解</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="6-访问标志（access-flag、访问标识、访问标记）"><a href="#6-访问标志（access-flag、访问标识、访问标记）" class="headerlink" title="6.访问标志（access_flag、访问标识、访问标记）"></a>6.访问标志（access_flag、访问标识、访问标记）</h4><blockquote>
<p>在常量池后，紧跟着访问标记</p>
<p>该标记使用两个字节表示，用于识别一些类或者接口层次的访问信息</p>
<p>包括：这个 Class 是类还是接口；是否定义为 public 类型；是否定义为 abstract 类型；如果是类的话，是否被声明为 final 等</p>
<p>各种访问标记如下所示：</p>
<table>
<thead>
<tr>
<th align="left">标志名称</th>
<th>标志值</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">ACC_PUBLIC</td>
<td>0x0001</td>
<td align="left">标志为 public 类型</td>
</tr>
<tr>
<td align="left">ACC_FINAL</td>
<td>0x0010</td>
<td align="left">标志被声明为 final，只有类可以设置</td>
</tr>
<tr>
<td align="left">ACC_SUPER</td>
<td>0x0020</td>
<td align="left">标志允许使用 invokespecial 字节码指令的新语义，JDK1.0.2 之后编译出来的类的这个标志默认为真。（使用增强的方法调用父类方法）</td>
</tr>
<tr>
<td align="left">ACC_INTERFACE</td>
<td>0x0200</td>
<td align="left">标志这是一个接口</td>
</tr>
<tr>
<td align="left">ACC_ABSTRACT</td>
<td>0x0400</td>
<td align="left">是否为 abstract 类型，对于接口或者抽象类来说，次标志值为真，其他类型为假</td>
</tr>
<tr>
<td align="left">ACC_SYNTHETIC</td>
<td>0x1000</td>
<td align="left">标志此类并非由用户代码产生（即：由编译器产生的类，没有源码对应）</td>
</tr>
<tr>
<td align="left">ACC_ANNOTATION</td>
<td>0x2000</td>
<td align="left">标志这是一个注解</td>
</tr>
<tr>
<td align="left">ACC_ENUM</td>
<td>0x4000</td>
<td align="left">标志这是一个枚举</td>
</tr>
</tbody></table>
<p>类的访问权限通常为 ACC_开头的常量</p>
<p>每一种类型的表示都是通过设置访问标记的 32 位中的特定位来实现的</p>
<p>比如，若是 public final 的类，则该标记为 ACC_PUBLIC | ACC_FINAL</p>
<p>使用 ACC_SUPER 可以让类更准确地定位到父类的方法 super.method()，现代编译器都会设置并且使用这个标记</p>
</blockquote>
<ul>
<li><p>带有 ACC_INTERFACE 标志的 class 文件表示的是接口而不是类，反之则表示的是类而不是接口</p>
<blockquote>
<p>如果一个 class 文件被设置了 ACC_INTERFACE 标志，那么同时也得设置 ACC_ABSTRACT 标志。同时它不能再设置 ACC_FINAL、ACC_SUPER 或 ACC_ENUM 标志</p>
<p>如果没有设置 ACC_INTERFACE 标志，那么这个 class 文件可以具有上表中除 ACC_ANNOTATION 外的其他所有标志（<strong>ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标志除外，这两个标志不得同时设置</strong>）</p>
</blockquote>
</li>
<li><p>ACC_SUPER 标志用于确定类或接口里面的 <code>invokespecial</code> 指令使用的是哪一种执行语义</p>
<blockquote>
<p>针对 Java 虚拟机指令集的编译器都应当设置这个标志</p>
<p>对于 Java SE 8 及后续版本来说，无论 class 文件中这个标志的实际值是什么，也不管 class 文件的版本号是多少，Java 虚拟机都认为每个 class 文件均设置了 ACC_SUPER 标志</p>
<p><strong>ACC_SUPER 标志是为了向后兼容由旧 Java 编译器所编译的代码而设计的</strong></p>
<p>目前的 ACC_SUPER 标志在由 JDK1.0.2 之前的编译器所生成的 access_flags 中是没有确定含义的，如果设置了该标志，那么 Oracle 的 Java 虚拟机实现会将其忽略</p>
</blockquote>
</li>
<li><p>ACC_SYNTHETIC 标志意味着该类或接口是由编译器生成的，而不是由源代码生成的</p>
</li>
<li><p>注解类型必须设置 ACC_ANNOTATION 标志</p>
<blockquote>
<p>如果设置了 ACC_ANNOTATION 标志，那么也必须设置 ACC_INTERFACE 标志</p>
</blockquote>
</li>
<li><p>ACC_ENUM 标志表明该类或其父类为枚举类型</p>
</li>
</ul>
<h4 id="7-类索引、父类索引、接口索引"><a href="#7-类索引、父类索引、接口索引" class="headerlink" title="7.类索引、父类索引、接口索引"></a>7.类索引、父类索引、接口索引</h4><blockquote>
<p>在访问标记后，会指定该类的类别、父类类别以及实现的接口，格式如下：</p>
</blockquote>
<table>
<thead>
<tr>
<th>长度</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td>u2</td>
<td align="left">this_class</td>
</tr>
<tr>
<td>u2</td>
<td align="left">super_class</td>
</tr>
<tr>
<td>u2</td>
<td align="left">interfaces_count</td>
</tr>
<tr>
<td>u2</td>
<td align="left">interfaces[interfaces_count]</td>
</tr>
</tbody></table>
<ul>
<li><p>这三项数据来确定这个类的继承关系：</p>
<ul>
<li><p>类索引用于确定这个类的全限定名</p>
</li>
<li><p>父类索引用于确定这个类的父类的全限定名</p>
<blockquote>
<p>由于 Java 语言不允许多重继承，所以父类索引只有一个</p>
<p>除了 <code>java.lang.Object</code> 之外，所有的 Java 类都有父类，因此除了 java.lang.Object 外，所有 Java 类的父类索引都不为 e</p>
</blockquote>
</li>
<li><p>接口索引集合就用来描述这个类实现了哪些接口，这些被实现的接口将按 <code>implements</code> 语句（如果这个类本身是一个接口，则应当是 <code>extends</code> 语句）后的接口顺序从左到右排列在接口索引集合中</p>
</li>
</ul>
</li>
<li><p>this_class（类索引）</p>
<blockquote>
<p><strong>2 字节无符号整数，指向常量池的索引</strong></p>
<p>它提供了类的全限定名，如 <code>com/atguigu/java1/Demo</code></p>
<p>this_class 的值必须是对常量池表中某项的一个有效索引值</p>
<p>常量池在这个索引处的成员必须为 <code>CONSTANT_Class_info</code> 类型结构体，该结构体表示这个 class 文件所定义的类或接口</p>
</blockquote>
</li>
<li><p>super_class（父类索引）</p>
<blockquote>
<p><strong>2 字节无符号整数，指向常量池的索引</strong></p>
<p>它提供了当前类的父类的全限定名。如果我们没有继承任何类，其默认继承的是 <code>java/lang/object</code> 类</p>
<p>由于 Java 不支持多继承，所以其父类只有一个</p>
<p><strong>super_class 指向的父类不能是 final</strong></p>
</blockquote>
</li>
<li><p>interfaces</p>
<blockquote>
<p><strong>指向常量池索引集合，它提供了一个符号引用到所有已实现的接口</strong></p>
<p>由于一个类可以实现多个接口，因此需要以数组形式保存多个接口的索引，表示接口的每个索引也是一个指向常量池的 CONSTANT_Class（当然这里就必须是接口，而不是类）</p>
</blockquote>
<ul>
<li><p>interfaces_count（接口计数器）</p>
<blockquote>
<p><code>interfaces_count</code> 项的值表示当前类&#x2F;接口 <code>implements</code> 的接口数量</p>
</blockquote>
</li>
<li><p>interfaces[]（接口索引集合）</p>
<blockquote>
<p><code>interfaces[]</code> 中每个成员的值必须是对常量池表中某项的有效索引值，它的长度为 <code>interfaces_count</code></p>
<p>每个成员 <code>interfaces[i]</code> 必须为 <code>CONSTANT_Class_info</code> 结构，其中 <code>0 &lt;= i &lt; interfaces_count</code></p>
<p>在 <code>interfaces[]</code> 中，各成员所表示的接口顺序和对应的源代码中给定的接口顺序（从左至右）一样，<strong>即 interfaces[0] 对应的是源代码中最左边的接口</strong></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="8-字段表集合"><a href="#8-字段表集合" class="headerlink" title="8.字段表集合"></a>8.字段表集合</h4><ol>
<li><p>字段 field</p>
<blockquote>
<p><strong>用于描述接口或类中声明的变量</strong></p>
<p>字段（field）包括<strong>类级变量以及实例级变量</strong>，<strong>但是不包括方法内部、代码块内部声明的局部变量</strong></p>
<p>字段叫什么名字、字段被定义为什么数据类型，这些都是无法固定的，只能引用常量池中的常量来描述</p>
<p>它指向常量池索引集合，它描述了每个字段的完整信息</p>
<p>比如<strong>字段的标识符、访问修饰符（public、private 或 protected）、是类变量还是实例变量（static 修饰符）、是否是常量（final 修饰符）</strong>等</p>
</blockquote>
<ul>
<li><p>字段表集合中不会列出从父类或者实现的接口中继承而来的字段，但有可能列出原本 Java 代码之中不存在的字段</p>
<blockquote>
<p>比如在内部类中为了保持对外部类的访问性，会自动添加指向外部类实例的字段</p>
</blockquote>
</li>
<li><p><strong>在 Java 语言中字段是无法重载的，两个字段的数据类型、修饰符不管是否相同，都必须使用不同的名称</strong></p>
<blockquote>
<p><strong>但是对于字节码来讲，如果两个字段的描述符不一致，那字段重名就是合法的</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>字段计数器 fields_count</p>
<blockquote>
<p>fields_count 的值表示当前 class 文件 fields 表的成员个数</p>
<p>使用两个字节来表示</p>
<p>fields 表中每个成员都是一个 field_info 结构，用于表示该类或接口所声明的所有类字段或者实例字段，不包括方法内部声明的变量，也不包括从父类或父接口继承的那些字段</p>
</blockquote>
</li>
<li><p>字段表</p>
<table>
<thead>
<tr>
<th align="left">标志名称</th>
<th align="left">标志值</th>
<th align="left">含义</th>
<th align="left">数量</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2</td>
<td align="left">access_flags</td>
<td align="left">访问标志</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">name_index</td>
<td align="left">字段名索引</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">descriptor_index</td>
<td align="left">描述符索引</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">attributes_count</td>
<td align="left">属性计数器</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">attribute_info</td>
<td align="left">attributes</td>
<td align="left">属性集合</td>
<td align="left">attributes_count</td>
</tr>
</tbody></table>
<ul>
<li><p>字段表访问标识（access_flags）</p>
<blockquote>
<p>一个字段可以被各种关键字去修饰</p>
<p>比如：作用域修饰符（public、private、protected）、static 修饰符、final 修饰符、volatile 修饰符等等</p>
<p>因此，其可像类的访问标志那样，使用一些标志来标记字段。字段的访问标志有如下这些：</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">标志名称</th>
<th align="left">标志值</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">ACC_PUBLIC</td>
<td align="left">0x0001</td>
<td align="left">字段是否为 public</td>
</tr>
<tr>
<td align="left">ACC_PRIVATE</td>
<td align="left">0x0002</td>
<td align="left">字段是否为 private</td>
</tr>
<tr>
<td align="left">ACC_PROTECTED</td>
<td align="left">0x0004</td>
<td align="left">字段是否为 protected</td>
</tr>
<tr>
<td align="left">ACC_STATIC</td>
<td align="left">0x0008</td>
<td align="left">字段是否为 static</td>
</tr>
<tr>
<td align="left">ACC_FINAL</td>
<td align="left">0x0010</td>
<td align="left">字段是否为 final</td>
</tr>
<tr>
<td align="left">ACC_VOLATILE</td>
<td align="left">0x0040</td>
<td align="left">字段是否为 volatile</td>
</tr>
<tr>
<td align="left">ACC_TRANSTENT</td>
<td align="left">0x0080</td>
<td align="left">字段是否为 transient</td>
</tr>
<tr>
<td align="left">ACC_SYNCHETIC</td>
<td align="left">0x1000</td>
<td align="left">字段是否为由编译器自动产生</td>
</tr>
<tr>
<td align="left">ACC_ENUM</td>
<td align="left">0x4000</td>
<td align="left">字段是否为 enum</td>
</tr>
</tbody></table>
</li>
<li><p>字段名索引（name_index）</p>
<blockquote>
<p>根据字段名索引的值，查询常量池中指定索引项得到字段名</p>
</blockquote>
</li>
<li><p>描述符索引（ descriptor_index）</p>
<blockquote>
<p>描述符的作用是用来描述字段的数据类型、方法的参数列表（包括数量、类型以及顺序）和返回值</p>
<p>根据描述符规则，基本数据类型（byte，char，double，float，int，long，short，boolean）及代表无返回值的 void 类型都用一个大写字符来表示，而对象则用字符 L 加对象的全限定名来表示</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">标志符</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">B</td>
<td align="left">基本数据类型 byte</td>
</tr>
<tr>
<td align="left">C</td>
<td align="left">基本数据类型 char</td>
</tr>
<tr>
<td align="left">D</td>
<td align="left">基本数据类型 double</td>
</tr>
<tr>
<td align="left">F</td>
<td align="left">基本数据类型 float</td>
</tr>
<tr>
<td align="left">I</td>
<td align="left">基本数据类型 int</td>
</tr>
<tr>
<td align="left">J</td>
<td align="left">基本数据类型 long</td>
</tr>
<tr>
<td align="left">S</td>
<td align="left">基本数据类型 short</td>
</tr>
<tr>
<td align="left">Z</td>
<td align="left">基本数据类型 boolean</td>
</tr>
<tr>
<td align="left">V</td>
<td align="left">代表 void 类型</td>
</tr>
<tr>
<td align="left">L</td>
<td align="left">对象类型，比如：<code>Ljava/lang/Object;</code></td>
</tr>
<tr>
<td align="left">[</td>
<td align="left">数组类型，代表一维数组。比如：&#96;double[][][] is [[[D</td>
</tr>
</tbody></table>
</li>
<li><p>属性计数器（attributes_count）</p>
<blockquote>
<p>两个字节，表示属性表集合中属性个数</p>
</blockquote>
</li>
<li><p>属性表集合（attributes）</p>
<blockquote>
<p>一个字段还可能拥有一些属性，用于存储更多的额外信息</p>
<p>比如初始化值、一些注释信息等</p>
<p>属性个数存放在 attribute_count 中，属性具体内容存放在 attributes 数组中</p>
<p><strong>对于常量属性而言，attribute_length 值恒为 2</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 以常量属性为例，结构为：</span></span><br><span class="line">ConstantValue_attribute&#123;</span><br><span class="line">	u2 attribute_name_index;</span><br><span class="line">    <span class="comment">// 对于常量属性而言，attribute_length 值恒为 2</span></span><br><span class="line">	u4 attribute_length;</span><br><span class="line">    u2 constantvalue_index;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ol>
<h4 id="9-方法表集合"><a href="#9-方法表集合" class="headerlink" title="9.方法表集合"></a>9.方法表集合</h4><ol>
<li><p>方法 methods</p>
<blockquote>
<p>指向常量池索引集合，它完整描述了每个方法的签名</p>
</blockquote>
<ul>
<li><p>在字节码文件中，每一个 <code>method_info</code> 项都对应着一个类或者接口中的方法信息</p>
<blockquote>
<p>比如方法的访问修饰符（public、private、protected），方法的返回值类型以及方法的参数信息等</p>
</blockquote>
</li>
<li><p>如果这个方法不是抽象的或者不是 native 的，那么字节码中会体现出来</p>
</li>
<li><p>methods 表只描述当前类或接口中声明的方法，不包括从父类或父接口继承的方法</p>
</li>
<li><p>methods 表有可能会出现由编译器自动添加的方法</p>
<blockquote>
<p>最典型的便是编译器产生的方法信息</p>
<p>比如：类（接口）初始化方法 <code>&lt;clinit&gt;()</code> 和实例初始化方法 <code>&lt;init&gt;()</code></p>
</blockquote>
</li>
<li><p>使用注意事项</p>
<blockquote>
<p>在 Java 语言中，要重载（Overload）一个方法，除了要与原方法具有相同的简单名称之外，还要求必须拥有一个与原方法不同的特征签名，特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合，也就是因为返回值不会包含在特征签名之中，因此 Java 语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载</p>
<p>但在 Class 文件格式中，特征签名的范围更大一些，只要描述符不是完全一致的两个方法就可以共存。也就是说，如果两个方法有相同的名称和特征签名，但返回值不同，那么也是可以合法共存于同一个 class 文件中</p>
<p><strong>也就是说，尽管 Java 语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法，但是和 Java 语法规范相反，字节码文件中却恰恰允许存放多个方法签名相同的方法，唯一的条件就是这些方法之间的返回值不能相同</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>方法计数器 methods_count</p>
<blockquote>
<p>methods_count 的值表示当前 class 文件 methods 表的成员个数。使用两个字节来表示</p>
<p>methods 表中每个成员都是一个 method_info 结构</p>
</blockquote>
</li>
<li><p>方法表 methods[]</p>
<blockquote>
<p>methods 表中的每个成员都必须是一个 method_info 结构，用于表示当前类或接口中某个方法的完整描述</p>
<p>如果某个 method_info 结构的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志，那么该结构中也应包含实现这个方法所用的 Java 虚拟机指令</p>
<p>method_info 结构可以表示类和接口中定义的所有方法，包括实例方法、类方法、实例初始化方法和类或接口初始化方法</p>
<p>方法表的结构实际跟字段表是一样的，方法表结构如下：</p>
<p>（只有访问标志与字段表有区别）</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">标志名称</th>
<th align="left">标志值</th>
<th align="left">含义</th>
<th align="left">数量</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2</td>
<td align="left">access_flags</td>
<td align="left">访问标志</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">name_index</td>
<td align="left">方法名索引</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">descriptor_index</td>
<td align="left">描述符索引</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">attributes_count</td>
<td align="left">属性计数器</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">attribute_info</td>
<td align="left">attributes</td>
<td align="left">属性集合</td>
<td align="left">attributes_count</td>
</tr>
</tbody></table>
<ul>
<li><p><strong>方法表访问标志</strong></p>
<table>
<thead>
<tr>
<th align="left">标志名称</th>
<th align="left">标志值</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">ACC_PUBLIC</td>
<td align="left">0x0001</td>
<td align="left">public，方法可以从包外访问</td>
</tr>
<tr>
<td align="left">ACC_PRIVATE</td>
<td align="left">0x0002</td>
<td align="left">private，方法只能本类访问</td>
</tr>
<tr>
<td align="left">ACC_PROTECTED</td>
<td align="left">0x0004</td>
<td align="left">protected，方法在自身和子类可以访问</td>
</tr>
<tr>
<td align="left">ACC_STATIC</td>
<td align="left">0x0008</td>
<td align="left">static，静态方法</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
</ol>
<h4 id="10-属性表集合"><a href="#10-属性表集合" class="headerlink" title="10.属性表集合"></a>10.属性表集合</h4><ol>
<li><p>属性 attributes</p>
<blockquote>
<p>方法表集合之后的属性表集合，<strong>指的是 class 文件所携带的辅助信息</strong></p>
<p>比如该 class 文件的源文件的名称，以及任何带有 <code>RetentionPolicy.CLASS</code> 或者 <code>RetentionPolicy.RUNTIME</code> 的注解</p>
<p>这类信息通常被用于 Java 虚拟机的验证和运行，以及 Java 程序的调试，一般无须深入了解</p>
<p>此外，字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息</p>
<p>属性表集合的限制没有那么严格，不再要求各个属性表具有严格的顺序，并且只要不与已有的属性名重复，任何人实现的编译器都可以向属性表中写入自己定义的属性信息，但 Java 虚拟机运行时会忽略掉它不认识的属性</p>
</blockquote>
</li>
<li><p>属性计数器 attributes_count </p>
<blockquote>
<p>attributes_count 的值表示当前 class 文件属性表的成员个数</p>
<p>属性表中每一项都是一个 attribute_info 结构</p>
</blockquote>
</li>
<li><p>属性表 attributes[]</p>
<blockquote>
<p>属性表的每个项的值必须是 attribute_info 结构。属性表的结构比较灵活，各种不同的属性只要满足以下结构即可</p>
</blockquote>
<ul>
<li><p>属性的通用格式</p>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">名称</th>
<th align="left">数量</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2</td>
<td align="left">attribute_name_index</td>
<td align="left">1</td>
<td align="left">属性名索引</td>
</tr>
<tr>
<td align="left">u4</td>
<td align="left">attribute_length</td>
<td align="left">1</td>
<td align="left">属性长度</td>
</tr>
<tr>
<td align="left">u1</td>
<td align="left">info</td>
<td align="left">attribute_length</td>
<td align="left">属性表</td>
</tr>
</tbody></table>
</li>
<li><p><strong>属性类型</strong></p>
<blockquote>
<p>属性表实际上可以有很多类型，上面看到的 Code 属性只是其中一种，Java8 里面定义了 23 种属性。下面这些是虚拟机中预定义的属性：</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">属性名称</th>
<th align="left">使用位置</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Code</td>
<td align="left">方法表</td>
<td align="left">Java 代码编译成的字节码指令</td>
</tr>
<tr>
<td align="left">ConstantValue</td>
<td align="left">字段表</td>
<td align="left">final 关键字定义的常量池</td>
</tr>
<tr>
<td align="left">Deprecated</td>
<td align="left">类，方法，字段表</td>
<td align="left">被声明为 deprecated 的方法和字段</td>
</tr>
<tr>
<td align="left">Exceptions</td>
<td align="left">方法表</td>
<td align="left">方法抛出的异常</td>
</tr>
<tr>
<td align="left">EnclosingMethod</td>
<td align="left">类文件</td>
<td align="left">仅当一个类为局部类或者匿名类时才能拥有这个属性，这个属性用于标识这个类所在的外围方法</td>
</tr>
<tr>
<td align="left">InnerClass</td>
<td align="left">类文件</td>
<td align="left">内部类列表</td>
</tr>
<tr>
<td align="left">LineNumberTable</td>
<td align="left">Code 属性</td>
<td align="left">Java 源码的行号与字节码指令的对应关系</td>
</tr>
<tr>
<td align="left">LocalVariableTable</td>
<td align="left">Code 属性</td>
<td align="left">方法的局部变量描述</td>
</tr>
<tr>
<td align="left">StackMapTable</td>
<td align="left">Code 属性</td>
<td align="left">JDK1.6 中新增的属性，供新的类型检查检验器和处理目标方法的局部变量和操作数有所需要的类是否匹配</td>
</tr>
<tr>
<td align="left">Signature</td>
<td align="left">类，方法表，字段表</td>
<td align="left">用于支持泛型情况下的方法签名</td>
</tr>
<tr>
<td align="left">SourceFile</td>
<td align="left">类文件</td>
<td align="left">记录源文件名称</td>
</tr>
<tr>
<td align="left">SourceDebugExtension</td>
<td align="left">类文件</td>
<td align="left">用于存储额外的调试信息</td>
</tr>
<tr>
<td align="left">Synthetic</td>
<td align="left">类，方法表，字段表</td>
<td align="left">标志方法或字段为编译器自动生成的</td>
</tr>
<tr>
<td align="left">LocalVariableTypeTable</td>
<td align="left">类</td>
<td align="left">是哟很难过特征签名代替描述符，是为了引入泛型语法之后能描述泛型参数化类型而添加</td>
</tr>
<tr>
<td align="left">RuntimeVisibleAnnotations</td>
<td align="left">类，方法表，字段表</td>
<td align="left">为动态注解提供支持</td>
</tr>
<tr>
<td align="left">RuntimeInvisibleAnnotations</td>
<td align="left">类，方法表，字段表</td>
<td align="left">用于指明哪些注解是运行时不可见的</td>
</tr>
<tr>
<td align="left">RuntimeVisibleParameterAnnotation</td>
<td align="left">方法表</td>
<td align="left">作用与 RuntimeVisibleAnnotations 属性类似，只不过作用对象或方法</td>
</tr>
<tr>
<td align="left">RuntimeInvisibleParameterAnnotation</td>
<td align="left">方法表</td>
<td align="left">作用与 RuntimeInvisibleAnnotations 属性类似，只不过作用对象或方法</td>
</tr>
<tr>
<td align="left">AnnotationDefault</td>
<td align="left">方法表</td>
<td align="left">用于记录注解类元素的默认值</td>
</tr>
<tr>
<td align="left">BootstrapMethods</td>
<td align="left">类文件</td>
<td align="left">用于保存 invokeddynamic 指令引用的引导方法限定符</td>
</tr>
</tbody></table>
</li>
<li><p>部分属性详解</p>
<ul>
<li><p>ConstantValue 属性</p>
<blockquote>
<p>ConstantValue 属性表示一个常量字段的值。位于 field_info 结构的属性表中</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ConstantValue_attribute&#123;</span><br><span class="line">	u2 attribute_name_index;</span><br><span class="line">	u4 attribute_length;</span><br><span class="line">	u2 constantvalue_index;<span class="comment">//字段值在常量池中的索引，常量池在该索引处的项给出该属性表示的常量值。（例如，值是1ong型的，在常量池中便是CONSTANT_Long）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Deprecated 属性</p>
<blockquote>
<p>Deprecated 属性是在 JDK1.1 为了支持注释中的关键词@deprecated 而引入的</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Deprecated_attribute&#123;</span><br><span class="line">	u2 attribute_name_index;</span><br><span class="line">	u4 attribute_length;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Code 属性</p>
<blockquote>
<p>Code 属性就是存放方法体里面的代码</p>
<p>但是，并非所有方法表都有 Code 属性</p>
<p>像接口或者抽象方法，他们没有具体的方法体，因此也就不会有 Code 属性了</p>
<p>Code 属性表的前两项跟属性表是一致的，即 Code 属性表遵循属性表的结构，后面那些则是他自定义的结构</p>
<p>Code 属性表的结构，如下表：</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">名称</th>
<th align="left">数量</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2</td>
<td align="left">attribute_name_index</td>
<td align="left">1</td>
<td align="left">属性名索引</td>
</tr>
<tr>
<td align="left">u4</td>
<td align="left">attribute_length</td>
<td align="left">1</td>
<td align="left">属性长度</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">max_stack</td>
<td align="left">1</td>
<td align="left">操作数栈深度的最大值</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">max_locals</td>
<td align="left">1</td>
<td align="left">局部变量表所需的存续空间</td>
</tr>
<tr>
<td align="left">u4</td>
<td align="left">code_length</td>
<td align="left">1</td>
<td align="left">字节码指令的长度</td>
</tr>
<tr>
<td align="left">u1</td>
<td align="left">code</td>
<td align="left">code_lenth</td>
<td align="left">存储字节码指令</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">exception_table_length</td>
<td align="left">1</td>
<td align="left">异常表长度</td>
</tr>
<tr>
<td align="left">exception_info</td>
<td align="left">exception_table</td>
<td align="left">exception_length</td>
<td align="left">异常表</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">attributes_count</td>
<td align="left">1</td>
<td align="left">属性集合计数器</td>
</tr>
<tr>
<td align="left">attribute_info</td>
<td align="left">attributes</td>
<td align="left">attributes_count</td>
<td align="left">属性集合</td>
</tr>
</tbody></table>
</li>
<li><p>InnerClasses 属性</p>
<blockquote>
<p>为了方便说明特别定义一个表示类或接口的 Class 格式为 C</p>
<p>如果 C 的常量池中包含某个 CONSTANT_Class_info 成员，且这个成员所表示的类或接口不属于任何一个包，那么 C 的 ClassFile 结构的属性表中就必须含有对应的 InnerClasses 属性</p>
<p>InnerClasses 属性是在 JDK1.1 中为了支持内部类和内部接口而引入的，位于 ClassFile 结构的属性表</p>
</blockquote>
</li>
<li><p>LineNumberTable 属性</p>
<blockquote>
<p>LineNumberTable 属性是可选变长属性，位于 Code 结构的属性表</p>
<p>LineNumberTable 属性是用来描述 Java 源码行号与字节码行号之间的对应关系</p>
<p>这个属性可以用来在调试的时候定位代码执行的行数</p>
<ul>
<li>start_pc，即字节码行号；line_number，即 Java 源代码行号</li>
</ul>
<p>在 Code 属性的属性表中，LineNumberTable 属性可以按照任意顺序出现</p>
<p>多个 LineNumberTable 属性可以共同表示一个行号在源文件中表示的内容，即 LineNumberTable 属性不需要与源文件的行一一对应</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// LineNumberTable属性表结构：</span></span><br><span class="line">LineNumberTable_attribute&#123;</span><br><span class="line">    u2 attribute_name_index;</span><br><span class="line">    u4 attribute_length;</span><br><span class="line">    u2 line_number_table_length;</span><br><span class="line">    &#123;</span><br><span class="line">        u2 start_pc;</span><br><span class="line">        u2 line_number;</span><br><span class="line">    &#125; line_number_table[line_number_table_length];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>LocalVariableTable 属性</p>
<blockquote>
<p>LocalVariableTable 是可选变长属性，位于 Code 属性的属性表中</p>
<p>它被调试器用于确定方法在执行过程中局部变量的信息</p>
<p>在 Code 属性的属性表中，LocalVariableTable 属性可以按照任意顺序出现</p>
<p>Code 属性中的每个局部变量最多只能有一个 LocalVariableTable 属性</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// LocalVariableTable属性表结构：</span></span><br><span class="line">LocalVariableTable_attribute&#123;</span><br><span class="line">    u2 attribute_name_index;</span><br><span class="line">    u4 attribute_length;</span><br><span class="line">    u2 local_variable_table_length;</span><br><span class="line">    &#123;</span><br><span class="line">        u2 start_pc;</span><br><span class="line">        u2 length;</span><br><span class="line">        u2 name_index;</span><br><span class="line">        u2 descriptor_index;</span><br><span class="line">        u2 index;</span><br><span class="line">    &#125; local_variable_table[local_variable_table_length];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>start pc + length 表示这个变量在字节码中的生命周期起始和结束的偏移位置（this 生命周期从头 e 到结尾 10）</p>
<p>index 就是这个变量在局部变量表中的槽位<strong>（槽位可复用）</strong></p>
<p>name 就是变量名</p>
<p>Descriptor 表示局部变量类型描述</p>
</blockquote>
</li>
<li><p>Signature 属性</p>
<blockquote>
<p>Signature 属性是可选的定长属性，位于 ClassFile，field_info 或 method_info 结构的属性表中</p>
<p>在 Java 语言中，任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量（Type Variables）或参数化类型（Parameterized Types），则 Signature 属性会为它记录泛型签名信息</p>
</blockquote>
</li>
<li><p>SourceFile 属性</p>
<blockquote>
<p>长度为固定的 8 个字节。结构如下</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">名称</th>
<th align="left">数量</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">u2</td>
<td align="left">attribute_name_index</td>
<td align="left">1</td>
<td align="left">属性名索引</td>
</tr>
<tr>
<td align="left">u4</td>
<td align="left">attribute_length</td>
<td align="left">1</td>
<td align="left">属性长度</td>
</tr>
<tr>
<td align="left">u2</td>
<td align="left">sourcefile index</td>
<td align="left">1</td>
<td align="left">源码文件素引</td>
</tr>
</tbody></table>
</li>
<li><p>其他属性</p>
<blockquote>
<p>Java 虚拟机中预定义的属性有 20 多个，这里就不一一介绍了，通过上面几个属性的介绍，只要领会其精髓，其他属性的解读也是易如反掌</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="2-字节码指令集与解析举例"><a href="#2-字节码指令集与解析举例" class="headerlink" title="2.字节码指令集与解析举例"></a>2.字节码指令集与解析举例</h3><h4 id="1-概述"><a href="#1-概述" class="headerlink" title="1.概述"></a>1.概述</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E8%8A%82%E7%A0%81%E6%8C%87%E4%BB%A4%E6%A6%82%E8%BF%B00.png" alt="字节码指令概述0"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E8%8A%82%E7%A0%81%E6%8C%87%E4%BB%A4%E6%A6%82%E8%BF%B0.png" alt="字节码指令概述"></p>
<blockquote>
<p>大部分指令都没有支持 byte、char 和 short 类型，甚至没有任何指令支持 boolean 类型</p>
<p>编译器会在编译器或运行期</p>
<ul>
<li>将 byte 和 short 类型的数据<strong>带符号扩展（Sign-Extend）</strong>为相应的 int 类型数据</li>
<li>将 boolean 和 char 类型数据<strong>零扩展（Zero-Extend）</strong>为相应的 int 类型数据</li>
</ul>
<p>在处理 boolean、byte、short、char 类型的数组时也会转换为使用对应的 int 类型的字节码指令来处理</p>
<p>因此大多数对于 boolean、byte、short、char 类型数据的操作实际上都是使用相应的 int 类型作为运算类型</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E8%8A%82%E7%A0%81%E6%8C%87%E4%BB%A4%E6%A6%82%E8%BF%B02.png" alt="字节码指令概述2"></p>
<h4 id="2-加载与存储指令"><a href="#2-加载与存储指令" class="headerlink" title="2.加载与存储指令"></a>2.加载与存储指令</h4><ul>
<li><p>操作数栈、局部变量表复习</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%AD%98%E5%82%A8%E6%8C%87%E4%BB%A41.png" alt="加载与存储指令1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A8.png" alt="局部变量表"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%AD%98%E5%82%A8%E6%8C%87%E4%BB%A42.png" alt="加载与存储指令2"></p>
</li>
</ul>
<ol>
<li><p>概述</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%AD%98%E5%82%A8%E6%8C%87%E4%BB%A40.png" alt="加载与存储指令0"></p>
</li>
<li><p>局部变量压栈指令</p>
<ul>
<li><p>常用指令集</p>
<blockquote>
<p>iload 从局部变量中装载 int 类型值</p>
<p>lload 从局部变量中装载 long 类型值</p>
<p>fload 从局部变量中装载 float 类型值</p>
<p>dload 从局部变量中装载 double 类型值</p>
<p>aload 从局部变量中装载引用类型值（refernce）</p>
</blockquote>
<table>
<thead>
<tr>
<th>xload_n</th>
<th>xload_0</th>
<th>xload_1</th>
<th>xload_2</th>
<th>xload_3</th>
</tr>
</thead>
<tbody><tr>
<td><b>iload_n</b></td>
<td>iload_0</td>
<td>iload_1</td>
<td>iload_2</td>
<td>iload_3</td>
</tr>
<tr>
<td><b>lload_n</b></td>
<td>lload_0</td>
<td>lload_1</td>
<td>lload_2</td>
<td>lload_3</td>
</tr>
<tr>
<td><b>fload_n</b></td>
<td>fload_0</td>
<td>fload_1</td>
<td>fload_2</td>
<td>fload_3</td>
</tr>
<tr>
<td><b>dload_n</b></td>
<td>dload_0</td>
<td>dload_1</td>
<td>dload_2</td>
<td>dload_3</td>
</tr>
<tr>
<td><b>aload_n</b></td>
<td>aload_0</td>
<td>aload_1</td>
<td>aload_2</td>
<td>aload_3</td>
</tr>
</tbody></table>
</li>
<li><p>局部变量压栈指令剖析</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E5%8E%8B%E6%A0%88%E6%8C%87%E4%BB%A4.png" alt="局部变量压栈指令"></p>
<blockquote>
<p>举例</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">load</span><span class="params">(<span class="type">int</span> num, Object obj, <span class="type">long</span> count, <span class="type">boolean</span> flag, <span class="type">short</span>[] arr)</span> &#123;</span><br><span class="line">	System.out.println(num);</span><br><span class="line">    System.out.println(obj);</span><br><span class="line">    System.out.println(count);</span><br><span class="line">    System.out.println(flag);</span><br><span class="line">    System.out.println(arr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E5%8E%8B%E6%A0%88%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B.png" alt="局部变量压栈指令举例"></p>
</li>
</ul>
</li>
<li><p>常量入栈指令</p>
<ul>
<li><p>常用指令集</p>
<blockquote>
<p>aconst_null 将null对象引用压入栈</p>
<p>iconst_m1 将int类型常量-1压入栈</p>
<p>iconst_0 将int类型常量0压入栈</p>
<p>lconst_0 将long类型常量0压入栈</p>
<p>fconst_0 将float类型常量0压入栈</p>
<p>dconst_1 将double类型常量1压入栈</p>
<p>bipush 将一个8位带符号整数压入栈</p>
<p>sipush 将16位带符号整数压入栈</p>
<p>ldc 把常量池中的项压入栈</p>
<p>ldc_w 把常量池中的项压入栈（使用宽索引）</p>
<p>ldc2_w 把常量池中long类型或者double类型的项压入栈（使用宽索引）</p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">xconst_n</th>
<th align="center">范围</th>
<th>xconst_null</th>
<th align="center">xconst_m1</th>
<th align="center">xconst_0</th>
<th align="center">xconst_1</th>
<th align="center">xconst_2</th>
<th align="center">xconst_3</th>
<th align="center">xconst_4</th>
<th align="center">xconst_5</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>iconst_n</strong></td>
<td align="center">[-1, 5]</td>
<td></td>
<td align="center">iconst_m1</td>
<td align="center">iconst_0</td>
<td align="center">iconst_1</td>
<td align="center">iconst_2</td>
<td align="center">iconst_3</td>
<td align="center">iconst_4</td>
<td align="center">iconst_5</td>
</tr>
<tr>
<td align="center"><strong>lconst_n</strong></td>
<td align="center">0, 1</td>
<td></td>
<td align="center"></td>
<td align="center">lconst_0</td>
<td align="center">lconst_1</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>fconst_n</strong></td>
<td align="center">0, 1, 2</td>
<td></td>
<td align="center"></td>
<td align="center">fconst_0</td>
<td align="center">fconst_1</td>
<td align="center">fconst_2</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>dconst_n</strong></td>
<td align="center">0, 1</td>
<td></td>
<td align="center"></td>
<td align="center">dconst_0</td>
<td align="center">dconst_1</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>aconst_n</strong></td>
<td align="center">null, String literal, Class literal</td>
<td>aconst_null</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>bipush</strong></td>
<td align="center">一个字节，2^8^，[-2^7^, 2^7^ - 1]，即[-128, 127]</td>
<td></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>sipush</strong></td>
<td align="center">两个字节，2^16^，[-2^15^, 2^15^ - 1]，即[-32768, 32767]</td>
<td></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>ldc</strong></td>
<td align="center">四个字节，2^32^，[-2^31^, 2^31^ - 1]</td>
<td></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>ldc_w</strong></td>
<td align="center">宽索引</td>
<td></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"><strong>ldc2_w</strong></td>
<td align="center">宽索引，long或double</td>
<td></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
</li>
<li><p>常量入栈指令剖析</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E5%85%A5%E6%A0%88%E6%8C%87%E4%BB%A4.png" alt="常量入栈指令"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E5%85%A5%E6%A0%88%E6%8C%87%E4%BB%A42.png" alt="常量入栈指令2"></p>
<table>
    <tbody>  
        <tr>
            <th>类型</th> 
            <th>常数指令</th> 
            <th>范围</th> 
       </tr>
       <tr>
            <td rowspan="4">int(boolean,byte,char,short)</td>
            <td>iconst</td>
            <td>[-1, 5]</td>
       </tr>	
       <tr>
            <td>bipush</td>
            <td>[-128, 127]</td>
       </tr>
       <tr>
            <td>sipush</td>
            <td>[-32768, 32767]</td>
       </tr> 
       <tr>
            <td>ldc</td>
            <td>any int value</td>
       </tr>
       <tr>
            <td rowspan="2">long</td>
            <td>lconst</td>
            <td>0, 1</td>
       </tr>	
       <tr>
            <td>ldc</td>
            <td>any long value</td>
       </tr>
       <tr>
            <td rowspan="2">float</td>
            <td>fconst</td>
            <td>0, 1, 2</td>
       </tr>	
       <tr>
            <td>ldc</td>
            <td>any float value</td>
       </tr>
       <tr>
            <td rowspan="2">double</td>
            <td>dconst</td>
            <td>0, 1</td>
       </tr>	
       <tr>
            <td>ldc</td>
            <td>any double value</td>
       </tr> 
       <tr>
            <td rowspan="2">reference</td>
            <td>aconst</td>
            <td>null</td>
       </tr>
       <tr>
            <td>ldc</td>
            <td>String literal, Class literal</td>
       </tr>
   <tbody> 
</table>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E5%85%A5%E6%A0%88%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B.png" alt="常量入栈指令举例"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E5%85%A5%E6%A0%88%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B2.png" alt="常量入栈指令举例2"></p>
</li>
</ul>
</li>
<li><p>出栈装入局部变量表指令</p>
<ul>
<li><p>常用指令集</p>
<blockquote>
<p>istore 将int类型值存入局部变量</p>
<p>lstore 将long类型值存入局部变量</p>
<p>fstore 将float类型值存入局部变量</p>
<p>dstore 将double类型值存入局部变量</p>
<p>astore 将将引用类型或returnAddress类型值存入局部变量</p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">xstore_n</th>
<th align="center">xstore_0</th>
<th align="center">xstore_1</th>
<th align="center">xstore_2</th>
<th align="center">xstore_3</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>istore_n</strong></td>
<td align="center">istore_0</td>
<td align="center">istore_1</td>
<td align="center">istore_2</td>
<td align="center">istore_3</td>
</tr>
<tr>
<td align="center"><strong>lstore_n</strong></td>
<td align="center">lstore_0</td>
<td align="center">lstore_1</td>
<td align="center">lstore_2</td>
<td align="center">lstore_3</td>
</tr>
<tr>
<td align="center"><strong>fstore_n</strong></td>
<td align="center">fstore_0</td>
<td align="center">fstore_1</td>
<td align="center">fstore_2</td>
<td align="center">fstore_3</td>
</tr>
<tr>
<td align="center"><strong>dstore_n</strong></td>
<td align="center">dstore_0</td>
<td align="center">dstore_1</td>
<td align="center">dstore_2</td>
<td align="center">dstore_3</td>
</tr>
<tr>
<td align="center"><strong>astore_n</strong></td>
<td align="center">astore_0</td>
<td align="center">astore_1</td>
<td align="center">astore_2</td>
<td align="center">astore_3</td>
</tr>
</tbody></table>
</li>
<li><p>出栈装入局部变量表指令剖析</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%87%BA%E6%A0%88%E8%A3%85%E5%85%A5%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A8.png" alt="出栈装入局部变量表"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%87%BA%E6%A0%88%E8%A3%85%E5%85%A5%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A82.png" alt="出栈装入局部变量表2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%87%BA%E6%A0%88%E8%A3%85%E5%85%A5%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A83.png" alt="出栈装入局部变量表3"></p>
</li>
</ul>
</li>
</ol>
<h4 id="3-算术指令"><a href="#3-算术指令" class="headerlink" title="3.算术指令"></a>3.算术指令</h4><ol>
<li><p>算数指令集</p>
<table>
    <tbody>  
        <tr>
            <th colspan="2">算数指令</th> 
            <th>int(boolean,byte,char,short)</th> 
            <th>long</th>
            <th>float</th> 
              <th>double</th> 
       </tr>
       <tr>
            <td colspan="2">加法指令</td>
            <td>iadd</td>
            <td>ladd</td>
            <td>fadd</td>
            <td>dadd</td>
       </tr>	
       <tr>
            <td colspan="2">减法指令</td>
            <td>isub</td>
            <td>lsub</td>
            <td>fsub</td>
            <td>dsub</td>
       </tr> 
       <tr>
            <td colspan="2">乘法指令</td>
            <td>imul</td>
            <td>lmul</td>
            <td>fmul</td>
            <td>dmul</td>
       </tr> 
       <tr>
            <td colspan="2">除法指令</td>
            <td>idiv</td>
            <td>ldiv</td>
            <td>fdiv</td>
            <td>ddiv</td>
       </tr>
       <tr>
            <td colspan="2">求余指令</td>
            <td>irem</td>
            <td>lrem</td>
            <td>frem</td>
            <td>drem</td>
       </tr>
       <tr>
            <td colspan="2">取反指令</td>
            <td>ineg</td>
            <td>lneg</td>
            <td>fneg</td>
            <td>dneg</td>
       </tr>
       <tr>
            <td colspan="2">自增指令</td>
            <td>iinc</td>
            <td></td>
            <td></td>
            <td></td>
       </tr>
       <tr>
            <td rowspan="4">位运算指令</td>
            <td>按位或指令</td>
            <td>ior</td>
            <td>lor</td>
            <td></td>
            <td></td>
       </tr> 
       <tr>
            <td>按位或指令</td>
            <td>ior</td>
            <td>lor</td>
            <td></td>
            <td></td>
       </tr> 
       <tr>
            <td>按位与指令</td>
            <td>iand</td>
            <td>land</td>
            <td></td>
            <td></td>
       </tr>
       <tr>
            <td>按位异或指令</td>
            <td>ixor</td>
            <td>lxor</td>
            <td></td>
            <td></td>
       </tr> 
       <tr>
            <td colspan="2">比较指令</td>
            <td></td>
            <td>lcmp</td>
            <td>fcmpg / fcmpl</td>
            <td>dcmpg / dcmpl</td>
       </tr> 
   <tbody> 
</table>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%AF%94%E8%BE%83%E6%8C%87%E4%BB%A4%E8%AF%B4%E6%98%8E.png" alt="比较指令说明"></p>
</li>
<li><p>算数指令集举例</p>
<ul>
<li><p>举例1</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 入参 i = 5</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">bar</span><span class="params">(<span class="type">int</span> i)</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> ((i + <span class="number">1</span>) - <span class="number">2</span>) * <span class="number">3</span> / <span class="number">4</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

 <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B1.png" alt="算数指令举例1" style="zoom:50%;" />
</li>
<li><p>举例2</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">	<span class="type">byte</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">15</span>;</span><br><span class="line">	<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line">	<span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> i + j;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B2.png" alt="算数指令举例2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B22.png" alt="算数指令举例22"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B23.png" alt="算数指令举例23"></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B24.gif" alt="算数指令举例24"></p>
</li>
<li><p>举例3</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">	<span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">500</span>;</span><br><span class="line">	<span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">	<span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> x / y;</span><br><span class="line">	<span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">50</span>;</span><br><span class="line">	System.out.println(a + b);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B3.gif" alt="算数指令举例3"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%AE%97%E6%95%B0%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B32.gif" alt="算数指令举例32"></p>
</li>
</ul>
</li>
<li><p><strong><code>i++</code> 和 <code>++i</code> 问题</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    i = i++;</span><br><span class="line">    System.out.println(<span class="string">&quot;i=&quot;</span> + i);		 <span class="comment">// i=10</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    j = ++j;</span><br><span class="line">    System.out.println(<span class="string">&quot;j=&quot;</span> + j);		 <span class="comment">// j=11</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">temp</span> <span class="operator">=</span> ++k + k++;				 <span class="comment">// temp = 11 + 11</span></span><br><span class="line">    System.out.println(<span class="string">&quot;k=&quot;</span> + k);		 <span class="comment">// k=12</span></span><br><span class="line">    System.out.println(<span class="string">&quot;temp=&quot;</span> + temp);	 <span class="comment">// temp=22</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    m = ++m + m++;						 <span class="comment">// m = 11 + 11</span></span><br><span class="line">    System.out.println(<span class="string">&quot;m=&quot;</span> + m);		 <span class="comment">// m=22</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">l</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">temp2</span> <span class="operator">=</span> l++ + ++l;				 <span class="comment">// temp2 = 10 + 12</span></span><br><span class="line">    System.out.println(<span class="string">&quot;l=&quot;</span> + l);		 <span class="comment">// l=12</span></span><br><span class="line">    System.out.println(<span class="string">&quot;temp2=&quot;</span> + temp2);<span class="comment">// temp2=22</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    n = n++ + ++n;						 <span class="comment">// n = 10 + 12</span></span><br><span class="line">    System.out.println(<span class="string">&quot;n=&quot;</span> + n);		 <span class="comment">// n=12</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>补充：</p>
<p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/442841926">for 循环中应该用 i++ 还是 ++i 的问题</a></p>
<p>结论：++i 效率高，少了一个局部变量。但是由于编译器优化，i++ 会被优化成 ++i</p>
</blockquote>
</li>
</ol>
<h4 id="4-类型转换指令"><a href="#4-类型转换指令" class="headerlink" title="4.类型转换指令"></a>4.类型转换指令</h4><ol>
<li><p>类型转换指令集</p>
<blockquote>
<p>主要指将除了 boolean 类型之外的其中数值类型之间的转换</p>
<p>这些转换操作一般用于实现用户代码中的<strong>显式类型转换操作</strong></p>
<p>或者<strong>用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应问题</strong></p>
<p><strong>左下角为窄化 VS 右上角为宽化</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center"><strong>byte</strong></th>
<th align="center"><strong>char</strong></th>
<th align="center"><strong>short</strong></th>
<th align="center"><strong>int</strong></th>
<th align="center"><strong>long</strong></th>
<th align="center"><strong>float</strong></th>
<th align="center"><strong>double</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>int</strong></td>
<td align="center">i2b</td>
<td align="center">i2c</td>
<td align="center">i2s</td>
<td align="center">○</td>
<td align="center">i2l</td>
<td align="center">i2f</td>
<td align="center">i2d</td>
</tr>
<tr>
<td align="center"><strong>long</strong></td>
<td align="center">l2i i2b</td>
<td align="center">l2i i2c</td>
<td align="center">l2i i2s</td>
<td align="center">l2i</td>
<td align="center">○</td>
<td align="center">l2f</td>
<td align="center">l2d</td>
</tr>
<tr>
<td align="center"><strong>float</strong></td>
<td align="center">f2i i2b</td>
<td align="center">f2i i2c</td>
<td align="center">f2i i2s</td>
<td align="center">f2i</td>
<td align="center">f2l</td>
<td align="center">○</td>
<td align="center">f2d</td>
</tr>
<tr>
<td align="center"><strong>double</strong></td>
<td align="center">d2i i2b</td>
<td align="center">d2i i2c</td>
<td align="center">d2i i2s</td>
<td align="center">d2i</td>
<td align="center">d2l</td>
<td align="center">d2f</td>
<td align="center">○</td>
</tr>
</tbody></table>
</li>
<li><p>宽化类型转换剖析（Widening Numeric Conversions 小范围 -&gt; 大范围）</p>
<ul>
<li><p>转换规则</p>
<blockquote>
<p>Java虚拟机直接支持以下数值的宽化类型转换，并不需要指令执行</p>
<ul>
<li>从 int 类型到 long、float 或者 double 类型。对应的指令为：i21、i2f、i2d</li>
<li>从 long 类型到 float、 double 类型。对应的指令为：i2f、i2d</li>
<li>从 float 类型到 double 类型。对应的指令为：f2d</li>
</ul>
<p>简化为：<code>int--&gt;long--&gt;float-&gt; double</code></p>
</blockquote>
</li>
<li><p>精度损失问题</p>
<ul>
<li><p>宽化类型转换是不会因为超过目标类型最大值而丢失信息的</p>
<blockquote>
<p>例如，从 int 转换到 long &#x2F; double，都不会丢失任何信息，转换前后的值是精确相等的</p>
</blockquote>
</li>
<li><p>从 int、long 类型数值转换到 float,或者 long 类型数值转换到 double 时，将可能发生精度丢失</p>
<blockquote>
<p>可能丢失掉几个最低有效位上的值，转换后的浮点数值是根据 IEEE754 最接近含入模式所得到的正确整数值</p>
<p><strong>尽管宽化类型转换实际上是可能发生精度丢失的，但是这种转换永远不会导致Java虚拟机抛出运行时异常</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>补充说明</p>
<blockquote>
<p>从 byte、char 和 short 类型到 int 类型的宽化类型转换实际上是不存在的</p>
<p>对于 byte 类型转为 int，虚拟机并没有做实质性的转化处理，只是简单地通过操作数栈交換了两个数据</p>
<p>而将byte转为long时，使用的是 i2l，可以看到在内部 byte 等同于 int 类型处理，类似的还有 short类型，这种处理方式有两个特点：</p>
<ul>
<li><p>一方面可以减少实际的数据类型，如果为 short 和 byte 都准备一套指令，那么指令的数量就会大増，而虚拟机目前的设计上，只愿意使用一个字节表示指令，因此指令总数不能超过256个，为了节省指令资源，将 short 和 byte 当做 int 处理也在情理之中</p>
</li>
<li><p>另一方面，由于局部变量表中的槽位固定为 32 位，无论是 byte 或者 short 存入局部变量表，都会占用32位空间。从这个角度说也没有必要特意区分这几种数据类型</p>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p>窄化类型转换剖析（Narrowing Numeric Conversion 大范围 -&gt; 小范围，也称<strong>强制类型转换</strong>）</p>
<ul>
<li><p>转换规则</p>
<blockquote>
<p>Java虚拟机也直接支持以下窄化类型转换：</p>
<ul>
<li>从 int 类型至 byte、 short 或者 char 类型。对应的指令有：i2b、i2c、i2s</li>
<li>从 long 类型到 int 类型。对应的指令有：l2i</li>
<li>从 float 类型到 int 或者 long 类型。对应的指令有：f2i、f2l</li>
<li>从 double 类型到 int、long 或者 float 类型。对应的指令有：d2i、d2l、d2f</li>
</ul>
</blockquote>
</li>
<li><p>精度损失问题</p>
<blockquote>
<p>窄化类型转换可能会导致转换结果具备不同的正负号、不同的数量级，因此转换过程很可能会导致数值丢失精度</p>
<p>尽管数据类型窄化转换可能会发生上限溢出、下限溢出和精度丢失等情况，<strong>但是Java虚拟机规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常</strong></p>
</blockquote>
</li>
<li><p>补充说明</p>
<ul>
<li><p>当将一个浮点值窄化转换为整数类型 T（T 限于 int 或 long 类型之一）的时候，将遵循以下转换规则：</p>
<blockquote>
<p>如果浮点值是 NaN，那转换结果就是 int 或 long 类型的 0</p>
<p>如果浮点值不是无穷大的话，浮点值使用 IEEE754 的向零含入模式取整，获得整数值 V</p>
<ul>
<li><p>如果 V 在目标类型 T（int 或 long）的表示范围之内，那转换结果就是 V</p>
</li>
<li><p>否则，将根据 V 的符号，转换为 T 所能表示的最大或者最小正数</p>
</li>
</ul>
</blockquote>
</li>
<li><p>当将一个 double 类型窄化转换为 float 类型时，将遵循以下转换规则</p>
<blockquote>
<p>通过向最接近数舍入模式舍入一个可以使用 float 类型表示的数字</p>
<p>如果转换结果的绝对值太小而无法使用 float 来表示，将返回 float 类型的正负零</p>
<p>如果转换结果的绝对值太大而无法使用 float 来表示，将返回float类型的正负无穷大</p>
<p>对于 double 类型的 NaN 值将按规定转換为 float 类型的 NaN 值</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="5-对象的创建与访问指令"><a href="#5-对象的创建与访问指令" class="headerlink" title="5.对象的创建与访问指令"></a>5.对象的创建与访问指令</h4><blockquote>
<p>Java是面向对象的程序设计语言，虚拟机平台从字节码层面就对面向对象做了深层次的支持</p>
<p>有一系列指令专门用于对象操作，可进一步细分为创建指令、字段访问指令、数组操作指令、类型检查指令</p>
</blockquote>
<ol>
<li><p>按对象和数组划分</p>
<ul>
<li><p>对象操作指令</p>
<ul>
<li><p>new 创建一个新对象</p>
</li>
<li><p>getfield 从对象中获取字段</p>
</li>
<li><p>putfield 设置对象中字段的值</p>
</li>
<li><p>getstatic 从类中获取静态字段</p>
</li>
<li><p>putstatic 设置类中静态字段的值</p>
</li>
<li><p>checkcast 确定对象为所给定的类型</p>
<blockquote>
<p>后跟目标类，判断栈顶元素是否为目标类 &#x2F; 接口的实例</p>
<p>如果不是便抛出异常</p>
</blockquote>
</li>
<li><p>instanceof 判断对象是否为给定的类型</p>
<blockquote>
<p>后跟目标类，判断栈顶元素是否为目标类 &#x2F; 接口的实例</p>
<p>是则压入 1，否则压入 0</p>
</blockquote>
</li>
</ul>
</li>
<li><p>数组操作指令</p>
<ul>
<li>newarray 分配数据成员类型为基本上数据类型的新数组</li>
<li>anewarray 分配数据成员类型为引用类型的新数组</li>
<li>arraylength 获取数组长度</li>
<li>multianewarray 分配新的多维数组</li>
</ul>
</li>
</ul>
</li>
<li><p>创建指令</p>
<table>
<thead>
<tr>
<th align="left">创建指令</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">new</td>
<td align="left">创建类实例</td>
</tr>
<tr>
<td align="left">newarray</td>
<td align="left">创建基本类型数组</td>
</tr>
<tr>
<td align="left">anewarray</td>
<td align="left">创建引用类型数组</td>
</tr>
<tr>
<td align="left">multilanewarra</td>
<td align="left">创建多维数组</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%88%9B%E5%BB%BA%E6%8C%87%E4%BB%A4.png" alt="创建指令"></p>
</li>
<li><p>字段访问指令</p>
<table>
<thead>
<tr>
<th align="left">字段访问指令</th>
<th align="left">含义</th>
</tr>
</thead>
<tbody><tr>
<td align="left">getstatic、putstatic</td>
<td align="left">访问类字段（static字段，或者称为类变量）的指令</td>
</tr>
<tr>
<td align="left">getfield、 putfield</td>
<td align="left">访问类实例字段（非static字段，或者称为实例变量）的指令</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E6%AE%B5%E8%AE%BF%E9%97%AE%E6%8C%87%E4%BB%A4.png" alt="字段访问指令"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E6%AE%B5%E8%AE%BF%E9%97%AE%E6%8C%87%E4%BB%A42.png" alt="字段访问指令2"></p>
</li>
<li><p>数组操作指令</p>
<table>
<thead>
<tr>
<th>数组指令</th>
<th>byte(boolean)</th>
<th>char</th>
<th>short</th>
<th>long</th>
<th>long</th>
<th>float</th>
<th>double</th>
<th>reference</th>
</tr>
</thead>
<tbody><tr>
<td><strong>xaload</strong></td>
<td>baload</td>
<td>caload</td>
<td>saload</td>
<td>iaload</td>
<td>laload</td>
<td>faload</td>
<td>daload</td>
<td>aaload</td>
</tr>
<tr>
<td><strong>xastore</strong></td>
<td>bastore</td>
<td>castore</td>
<td>sastore</td>
<td>iastore</td>
<td>lastore</td>
<td>fastore</td>
<td>dastore</td>
<td>aastore</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%95%B0%E7%BB%84%E6%93%8D%E4%BD%9C%E6%8C%87%E4%BB%A4.png" alt="数组操作指令"></p>
</li>
<li><p>类型检查指令</p>
<table>
<thead>
<tr>
<th>类型检查指令</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>instanceof</td>
<td>检查类型强制转换是否可以进行</td>
</tr>
<tr>
<td>checkcast</td>
<td>判断给定对象是否是某一个类的实例</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%9F%A5%E6%8C%87%E4%BB%A4.png" alt="类型检查指令"></p>
</li>
</ol>
<h4 id="6-方法调用与返回指令"><a href="#6-方法调用与返回指令" class="headerlink" title="6.方法调用与返回指令"></a>6.方法调用与返回指令</h4><ol>
<li><p>方法调用指令</p>
<table>
<thead>
<tr>
<th>方法调用指令</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>invokevirtual</td>
<td>调用对象的实例方法</td>
</tr>
<tr>
<td>invokeinterface</td>
<td>调用接口方法</td>
</tr>
<tr>
<td>invokespecial</td>
<td>调用一些需要特殊处理的实例方法，包括实例初始化方法（构造器）、私有方法和父类方法</td>
</tr>
<tr>
<td>invokestatic</td>
<td>调用命名类中的类方法（static方法）</td>
</tr>
<tr>
<td>invokedynamic</td>
<td>调用动态绑定的方法</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E6%8C%87%E4%BB%A4.png" alt="方法调用指令"></p>
</li>
<li><p>方法返回指令</p>
<table>
<thead>
<tr>
<th>方法返回指令</th>
<th>void</th>
<th>int</th>
<th>long</th>
<th>float</th>
<th>double</th>
<th>reference</th>
</tr>
</thead>
<tbody><tr>
<td><strong>xreturn</strong></td>
<td>return</td>
<td>ireturn</td>
<td>lreturn</td>
<td>freutrn</td>
<td>dreturn</td>
<td>areturn</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E6%8C%87%E4%BB%A40.png" alt="方法返回指令0"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E6%8C%87%E4%BB%A4.png" alt="方法返回指令"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">methodReturn</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">500</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">50</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> (i + j) / k;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E6%8C%87%E4%BB%A42.png" alt="方法返回指令2"></p>
</li>
</ol>
<h4 id="7-操作数栈管理指令"><a href="#7-操作数栈管理指令" class="headerlink" title="7.操作数栈管理指令"></a>7.操作数栈管理指令</h4><blockquote>
<p>nop 不做任何操作</p>
<p>pop 弹出栈顶端一个字长的内容</p>
<p>pop2 弹出栈顶端两个字长的内容</p>
<p>dup 复制栈顶部一个字长内容</p>
<p>dup_x1 复制栈顶部一个字长的内容，然后将复制内容及原来弹出的两个字长的内容压入栈</p>
<p>dup_x2 复制栈顶部一个字长的内容，然后将复制内容及原来弹出的三个字长的内容压入栈</p>
<p>dup2 复制栈顶部两个字长内容</p>
<p>dup2_x1 复制栈顶部两个字长的内容，然后将复制内容及原来弹出的三个字长的内容压入栈</p>
<p>dup2_x2 复制栈顶部两个字长的内容，然后将复制内容及原来弹出的四个字长的内容压入栈</p>
<p>swap 交换栈顶部两个字长内容</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88%E7%AE%A1%E7%90%86%E6%8C%87%E4%BB%A4.png" alt="操作数栈管理指令"></p>
<h4 id="8-控制转移指令"><a href="#8-控制转移指令" class="headerlink" title="8.控制转移指令"></a>8.控制转移指令</h4><ol>
<li><p>比较指令</p>
<blockquote>
<p>比较指令有： dcmpg、dcmpl、 fcmpg、fcmpl、lcmp</p>
<p>比较指令的作用是比较占栈顶两个元素的大小，并将比较结果入栈</p>
<p>与前面讲解的指令类似，首字符 d 表示 double 类型，f 表示 float，l 表示 long</p>
<p>对于 double 和 float 类型的数字，由于 NaN 的存在，各有两个版本的比较指令</p>
<p>以float为例，有 fcmpg 和 fcmpl 两个指令，它们的区别在于在数字比较时，若遇到NaN值，处理结果不同（指令 dcmpl 和 dcmpg 同理）</p>
</blockquote>
<blockquote>
<p>举例</p>
<p>指令 fcmp 和 fcmpl 都从中弹出两个操作数，并将它们做比较</p>
<p>设栈顶的元素为 V2，第 2 位的元素为 V1</p>
<ul>
<li>若 V1&#x3D;V2 则压入0</li>
<li>若 V1&gt;V2 则压入1</li>
<li>若 V1&lt;V2 则压入-1</li>
</ul>
<p>两个指令的不同之处在于，如果遇到NaN值， fcmpg会压入1，而 fcmpl 会压入-1</p>
</blockquote>
</li>
<li><p>条件跳转指令</p>
<table>
<thead>
<tr>
<th>&lt;</th>
<th>&lt;&#x3D;</th>
<th>&#x3D;&#x3D;</th>
<th>!&#x3D;</th>
<th>&gt;&#x3D;</th>
<th>&gt;</th>
<th>null</th>
<th>not null</th>
</tr>
</thead>
<tbody><tr>
<td>iflt</td>
<td>ifle</td>
<td>ifeq</td>
<td>ifng</td>
<td>ifge</td>
<td>ifgt</td>
<td>ifnull</td>
<td>ifnonnull</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%9D%A1%E4%BB%B6%E8%B7%B3%E8%BD%AC%E6%8C%87%E4%BB%A4.png" alt="条件跳转指令"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%9D%A1%E4%BB%B6%E8%B7%B3%E8%BD%AC%E6%8C%87%E4%BB%A42.png" alt="条件跳转指令2"></p>
</li>
<li><p>比较条件跳转指令</p>
<table>
<thead>
<tr>
<th>&lt;</th>
<th>&lt;&#x3D;</th>
<th>&#x3D;&#x3D;</th>
<th>!&#x3D;</th>
<th>&gt;&#x3D;</th>
<th>&gt;</th>
</tr>
</thead>
<tbody><tr>
<td>if_icmplt</td>
<td>if_icmple</td>
<td>if_icmpeq、if_acmpeq</td>
<td>if_icmpne、if_acmpne</td>
<td>if_icmpge</td>
<td>if_icmpgt</td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%AF%94%E8%BE%83%E6%9D%A1%E4%BB%B6%E8%B7%B3%E8%BD%AC%E6%8C%87%E4%BB%A4.png" alt="比较条件跳转指令"></p>
</li>
<li><p>多条件分支跳转</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A4%9A%E6%9D%A1%E4%BB%B6%E5%88%86%E6%94%AF%E8%B7%B3%E8%BD%AC.png" alt="多条件分支跳转"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A4%9A%E6%9D%A1%E4%BB%B6%E5%88%86%E6%94%AF%E8%B7%B3%E8%BD%AC2.png" alt="多条件分支跳转2"></p>
</li>
<li><p>无条件跳转</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%97%A0%E6%9D%A1%E4%BB%B6%E8%B7%B3%E8%BD%AC.png" alt="无条件跳转"></p>
</li>
</ol>
<h4 id="9-异常处理指令"><a href="#9-异常处理指令" class="headerlink" title="9.异常处理指令"></a>9.异常处理指令</h4><blockquote>
<p>athrow 抛出异常或错误。将栈顶异常抛出</p>
<p>jsr 跳转到子例程</p>
<p>jsr_w 跳转到子例程（宽索引）</p>
<p>rct 从子例程返回</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%8A%9B%E5%87%BA%E5%BC%82%E5%B8%B8%E6%8C%87%E4%BB%A4.png" alt="抛出异常指令"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A4%84%E7%90%86%E5%BC%82%E5%B8%B8%E6%8C%87%E4%BB%A4.png" alt="处理异常指令"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    System.out.println(method());	<span class="comment">// hello</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> str;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        str = <span class="string">&quot;zcm&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%BC%82%E5%B8%B8%E4%B8%BE%E4%BE%8B.png" alt="异常举例"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%BC%82%E5%B8%B8%E4%B8%BE%E4%BE%8B2.png" alt="异常举例2"></p>
<h4 id="10-同步控制指令"><a href="#10-同步控制指令" class="headerlink" title="10.同步控制指令"></a>10.同步控制指令</h4><blockquote>
<p>线程同步</p>
<p>montiorenter 进入并获取对象监视器。即：为栈顶对象加锁</p>
<p>monitorexit 释放并退出对象监视器。即：为栈顶对象解锁</p>
<p>Java虚拟机支持两种同步结构：方法级的同步和方法内部一段指令序列的同步，这两种同步都是使用monitor来支持的</p>
</blockquote>
<ol>
<li><p>方法级的同步</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E7%BA%A7%E7%9A%84%E5%90%8C%E6%AD%A51.png" alt="方法级的同步1"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span> &#123;</span><br><span class="line">	i++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E7%BA%A7%E7%9A%84%E5%90%8C%E6%AD%A52.png" alt="方法级的同步2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E7%BA%A7%E7%9A%84%E5%90%8C%E6%AD%A53.png" alt="方法级的同步3"></p>
</li>
<li><p>方法内指令序列的同步</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%86%85%E5%90%8C%E6%AD%A51.png" alt="方法内同步1"></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%86%85%E5%90%8C%E6%AD%A52.png" alt="方法内同步2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%86%85%E5%90%8C%E6%AD%A53.png" alt="方法内同步3"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%86%85%E5%90%8C%E6%AD%A54.png" alt="方法内同步4"></p>
</li>
</ol>
<h3 id="3-类的加载过程详解"><a href="#3-类的加载过程详解" class="headerlink" title="3.类的加载过程详解"></a>3.类的加载过程详解</h3><h4 id="1-概述-1"><a href="#1-概述-1" class="headerlink" title="1.概述"></a>1.概述</h4><ol>
<li><p>概述</p>
<blockquote>
<p>在 Java 中数据类型分为基本数据类型和引用数据类型</p>
<p><strong>基本数据类型由虚拟机预先定义，引用数据类型则需要进行类的加载</strong></p>
<p>按照 Java 虚拟机规范，从 class 文件到加载到内存中的类，到类卸载出内存为止，它的整个生命周期包括如下 7 个阶段：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E9%98%B6%E6%AE%B5%E6%A6%82%E8%BF%B0.png" alt="类的加载阶段概述"></p>
<p>其中，验证、准备、解析 3 个部分统称为链接（Linking）</p>
<p>从程序中类的使用过程看</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%A8%8B%E5%BA%8F%E4%B8%AD%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E5%9B%BE.png" alt="程序中类的加载流程图"></p>
</blockquote>
</li>
<li><p>大厂面试题</p>
<blockquote>
<p><strong>蚂蚁金服</strong></p>
<ul>
<li><p>描述一下 JVM 加载 Class 文件的原理机制？</p>
</li>
<li><p>一面：类加载过程</p>
</li>
</ul>
<p><strong>百度</strong></p>
<ul>
<li><p>类加载的时机</p>
</li>
<li><p>java 类加载过程？</p>
</li>
<li><p>简述 java 类加载机制？</p>
</li>
</ul>
<p><strong>腾讯</strong></p>
<ul>
<li>JVM 中类加载机制，类加载过程？</li>
</ul>
<p><strong>滴滴</strong></p>
<ul>
<li>JVM 类加载机制</li>
</ul>
<p><strong>美团</strong></p>
<ul>
<li><p>Java 类加载过程</p>
</li>
<li><p>描述一下 jvm 加载 class 文件的原理机制</p>
</li>
</ul>
<p><strong>京东</strong></p>
<ul>
<li><p>什么是类的加载？</p>
</li>
<li><p>哪些情况会触发类的加载？</p>
</li>
<li><p>讲一下 JVM 加载一个类的过程 JVM 的类加载机制是什么？</p>
</li>
</ul>
</blockquote>
</li>
</ol>
<h4 id="2-过程一：加载（Loading）阶段"><a href="#2-过程一：加载（Loading）阶段" class="headerlink" title="2.过程一：加载（Loading）阶段"></a>2.过程一：加载（Loading）阶段</h4><ol>
<li><p>加载完成的操作</p>
<ul>
<li><p>加载的理解</p>
<blockquote>
<p><strong>所谓加载，简而言之就是将Java类的字节码文件加载到机器内存中，并在内存中构建出Java类的原型——类模板对象</strong></p>
<p>所谓类模板对象，其实就是 Java 类在]VM 内存中的一个快照，JVM 将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中，这样]VM 在运行期便能通过类模板而获取 Java 类中的任意信息，能够对 Java 类的成员变量进行遍历，也能进行 Java 方法的调用</p>
<p><strong>反射的机制即基于这一基础。如果 JVM 没有将 Java 类的声明信息存储起来，则 JVM 在运行期也无法反射</strong></p>
</blockquote>
</li>
<li><p>加载完成的操作</p>
<blockquote>
<p><strong>加载阶段，简言之，查找并加载类的二进制数据，生成Class的实例</strong></p>
<p>在加载类时，Java 虚拟机必须完成以下 3 件事：</p>
<ul>
<li><p>通过类的全名，获取类的二进制数据流</p>
</li>
<li><p>解析类的二进制数据流为方法区内的数据结构（Java 类模型）</p>
</li>
<li><p>创建 java.lang.Class 类的实例，表示该类型。<strong>作为方法区这个类的各种数据的访问入口</strong></p>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p>二进制流的获取方式</p>
<blockquote>
<p>对于类的二进制数据流，虚拟机可以通过多种途径产生或获得（只要所读取的字节码符合 JVM 规范即可）</p>
<p>如果输入数据不是 ClassFile 的结构，则会抛出 <code>ClassFormatError</code></p>
</blockquote>
<ul>
<li>虚拟机可能通过文件系统读入一个 class 后缀的文件（最常见）</li>
<li>读入 jar、zip 等归档数据包，提取类文件</li>
<li>事先存放在数据库中的类的二进制数据</li>
<li>使用类似于 HTTP 之类的协议通过网络进行加载</li>
<li>在运行时生成一段 class 的二进制信息等</li>
<li>在获取到类的二进制信息后，Java 虚拟机就会处理这些数据，并最终转为一个 java.lang.Class 的实例</li>
</ul>
</li>
<li><p>类模型与 Class 实例的位置</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E6%A8%A1%E6%9D%BF%E4%B8%8Eclass%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%BD%8D%E7%BD%AE.png" alt="类模板与class对象的位置"></p>
<ul>
<li><p>类模型的位置</p>
<blockquote>
<p>加载的类在 JVM 中创建相应的类结构，类结构会存储在方法区（JDKl.8 之前：永久代；JDKl.8 及之后：元空间）</p>
</blockquote>
</li>
<li><p>Class 实例的位置</p>
<blockquote>
<p>类将.class 文件加载至元空间后，会在堆中创建一个 Java.lang.Class 对象，用来封装类位于方法区内的数据结构，该 Class 对象是在加载类的过程中创建的，每个类都对应有一个 Class 类型的对象</p>
</blockquote>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 反射举例</span></span><br><span class="line"><span class="type">Class</span> <span class="variable">clazz</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;java.lang.String&quot;</span>);</span><br><span class="line"><span class="comment">// 获取当前运行时类声明的所有方法</span></span><br><span class="line">Method[] ms = clazz.getDecla#FF0000Methods();</span><br><span class="line"><span class="keyword">for</span> (Method m : ms) &#123;</span><br><span class="line">    <span class="comment">// 获取方法的修饰符</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">mod</span> <span class="operator">=</span> Modifier.toString(m.getModifiers());</span><br><span class="line">    System.out.print(mod + <span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取方法的返回值类型</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">returnType</span> <span class="operator">=</span> (m.getReturnType()).getSimpleName();</span><br><span class="line">    System.out.print(returnType + <span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取方法名</span></span><br><span class="line">    System.out.print(m.getName() + <span class="string">&quot;(&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取方法的参数列表</span></span><br><span class="line">    Class&lt;?&gt;[] ps = m.getParameterTypes();</span><br><span class="line">    <span class="keyword">if</span> (ps.length == <span class="number">0</span>) &#123;</span><br><span class="line">        System.out.print(<span class="string">&#x27;)&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; ps.length; i++) &#123;</span><br><span class="line">        <span class="type">char</span> <span class="variable">end</span> <span class="operator">=</span> (i == ps.length - <span class="number">1</span>) ? <span class="string">&#x27;)&#x27;</span> : <span class="string">&#x27;,&#x27;</span>;</span><br><span class="line">        <span class="comment">// 获取参教的类型</span></span><br><span class="line">        System.out.print(ps[i].getSimpleName() + end);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>数组类的加载</p>
<blockquote>
<p>创建数组类的情况稍微有些特殊，因为<strong>数组类本身并不是由类加载器负责创建</strong>，而是由 JVM 在运行时根据需要而直接创建的，但数组的元素类型仍然需要依靠类加载器去创建</p>
</blockquote>
<ul>
<li><p>创建数组类（下述简称 A）的过程：</p>
<ul>
<li>如果数组的元素类型是引用类型，那么就遵循定义的加载过程递归加载和创建数组 A 的元素类型</li>
<li>JVM 使用指定的元素类型和数组维度来创建新的数组类</li>
</ul>
<blockquote>
<p>如果数组的元素类型是引用类型，数组类的可访问性就由元素类型的可访问性决定</p>
<p>否则数组类的可访问性将被缺省定义为 public</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="3-过程二：链接（Linking）阶段"><a href="#3-过程二：链接（Linking）阶段" class="headerlink" title="3.过程二：链接（Linking）阶段"></a>3.过程二：链接（Linking）阶段</h4><ol>
<li><p>环节1：验证（Verification）</p>
<blockquote>
<p>当类加载到系统后，就开始链接操作，验证是链接操作的第一步</p>
<p><strong>它的目的是保证加载的字节码是合法、合理并符合规范的</strong></p>
<p>验证的步骤比较复杂，实际要验证的项目也很繁多，大体上 Java 虚拟机需要做以下检查，如图所示</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E9%AA%8C%E8%AF%81%E9%98%B6%E6%AE%B5%E6%B5%81%E7%A8%8B.png" alt="验证阶段流程"></p>
</blockquote>
<blockquote>
<p><strong>整体说明：</strong></p>
<p>验证的内容则涵盖了类数据信息的格式验证、语义检查、字节码验证，以及符号引用验证等</p>
<ul>
<li><strong>其中格式验证会和加载阶段一起执行</strong></li>
<li>验证通过之后，类加载器才会成功将类的二进制数据信息加载到方法区中</li>
<li><strong>格式验证之外的验证操作将会在方法区中进行</strong></li>
</ul>
<p>链接阶段的验证虽然拖慢了加载速度，但它避免了在字节码运行时的各种检查（磨刀不误砍柴工）</p>
</blockquote>
<ul>
<li><p>格式验证</p>
<blockquote>
<p>是否以魔数 0XCAFEBABE 开头，主版本和副版本号是否在当前 Java 虚拟机的支持范围内，数据中每一个项是否都拥有正确的长度等</p>
<p><strong>格式验证会和加载阶段一起执行</strong></p>
</blockquote>
</li>
<li><p>语义检查</p>
<blockquote>
<p>Java 虚拟机会进行字节码的语义检查，但凡在语义上不符合规范的，虚拟机也不会给予验证通过</p>
<p>比如：</p>
<ul>
<li>是否所有的类都有父类的存在（在 Java 里，除了 object 外，其他类都应该有父类）</li>
<li>是否一些被定义为 final 的方法或者类被重写或继承了</li>
<li>非抽象类是否实现了所有抽象方法或者接口方法</li>
</ul>
</blockquote>
</li>
<li><p>字节码验证</p>
<blockquote>
<p>Java 虚拟机还会进行字节码验证，<strong>字节码验证也是验证过程中最为复杂的一个过程</strong></p>
<p>它试图通过对字节码流的分析，判断字节码是否可以被正确地执行</p>
<p>比如：</p>
<ul>
<li>在字节码的执行过程中，是否会跳转到一条不存在的指令</li>
<li>函数的调用是否传递了正确类型的参数</li>
<li>变量的赋值是不是给了正确的数据类型等</li>
</ul>
<p>栈映射帧（StackMapTable）就是在这个阶段，用于检测在特定的字节码处，其局部变量表和操作数栈是否有着正确的数据类型</p>
<p>但遗憾的是，100% 准确地判断一段字节码是否可以被安全执行是无法实现的，因此该过程只是尽可能地检查出可以预知的明显的问题</p>
<p>如果在这个阶段无法通过检查，虚拟机也不会正确装载这个类</p>
<p>但是，如果通过了这个阶段的检查，也不能说明这个类是完全没有问题的</p>
</blockquote>
</li>
<li><p>符号引用的验证</p>
<blockquote>
<p>在前面3次检查中，已经排除了文件格式错误、语义错误以及字节码的不正确性。但是依然不能确保类是没有问题的</p>
<p>校验器还将进符号引用的验证</p>
<p>Class 文件在其常量池会通过字符串记录自己将要使用的其他类或者方法。因此在验证阶段，<strong>虚拟机就会检查这些类或者方法确实是存在的</strong>，并且当前类有权限访问这些数据</p>
<ul>
<li><p>如果一个需要使用类无法在系统中找到，则会抛出 <code>NoClassDefFoundError</code></p>
</li>
<li><p>如果一个方法无法被找到，则会抛出 <code>NoSuchMethodError</code></p>
</li>
</ul>
<p><strong>此阶段在解析环节才会执行</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>环节2：准备（Preparation）</p>
<blockquote>
<p>准备阶段（Preparation），<strong>为类的静态变量（static 修饰）分配内存，并将其初始化为默认值</strong></p>
<p>当一个类验证通过时，虚拟机就会进入准备阶段</p>
<p>在这个阶段，虚拟机就会为这个类分配相应的内存空间，并设置默认初始值</p>
<p>Java 虚拟机为各类型变量默认的初始值如表所示</p>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">默认初始值</th>
</tr>
</thead>
<tbody><tr>
<td align="left">byte</td>
<td align="left">(byte)0</td>
</tr>
<tr>
<td align="left">short</td>
<td align="left">(short)0</td>
</tr>
<tr>
<td align="left">int</td>
<td align="left">0</td>
</tr>
<tr>
<td align="left">long</td>
<td align="left">0L</td>
</tr>
<tr>
<td align="left">float</td>
<td align="left">0.0f</td>
</tr>
<tr>
<td align="left">double</td>
<td align="left">0.0</td>
</tr>
<tr>
<td align="left">char</td>
<td align="left">\u0000</td>
</tr>
<tr>
<td align="left">boolean</td>
<td align="left">false</td>
</tr>
<tr>
<td align="left">reference</td>
<td align="left">null</td>
</tr>
</tbody></table>
<p>Java 并不支持 boolean 类型，对于 boolean 类型，内部实现是 int，由于 int 的默认值是 0，故对应的boolean 的默认值就是 false</p>
</blockquote>
<ul>
<li><p>注意事项</p>
<ul>
<li><p>不包含基本数据类型的字段用 <code>static final</code> 修饰的情况，因为 final 在编译的时候就会分配了，准备阶段会显式赋值</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 一般情况：static final 修饰的基本数据类型、字符串类型字面量会在准备阶段赋值</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;Hello world&quot;</span>;</span><br><span class="line"><span class="comment">// 特殊情况：static final 修饰的引用类型不会在准备阶段赋值，而是在初始化阶段赋值</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;Hello world&quot;</span>);</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>注意这里不会为实例变量分配初始化，类变量会分配在方法区中，而实例变量是会随着对象一起分配到 Java 堆中</strong></p>
</li>
<li><p><strong>在这个阶段并不会像初始化阶段中那样会有初始化或者代码被执行</strong></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>环节3：解析（Resolution）</p>
<blockquote>
<p>在准备阶段完成后，就进入了解析阶段</p>
<p><strong>解析阶段（Resolution）将类、接口、字段和方法的符号引用转为直接引用</strong></p>
<p>符号引用就是一些字面量的引用，和虚拟机的内部数据结构和和内存布局无关</p>
<p>比较容易理解的就是在 Class 类文件中，通过常量池进行了大量的符号引用。但是在程序实际运行时，只有符号引用是不够的，比如当如下 println() 方法被调用时，系统需要明确知道该方法的位置</p>
</blockquote>
<ul>
<li><p>举例</p>
<blockquote>
<p>输出操作 <code>System.out.println()</code> 对应的字节码</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">invokevirtual #<span class="number">24</span> &lt;java/io/PrintStream.println&gt;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%A7%A3%E6%9E%90%E9%98%B6%E6%AE%B5%E4%B8%BE%E4%BE%8B.png" alt="解析阶段举例"></p>
<p>Java 虚拟机为每个类都准备了一张方法表，将其所有的方法都列在表中，当需要调用一个类的方法的时候，只要知道这个方法在方法表中的偏移量就可以直接调用该方法</p>
<p><strong>通过解析操作，符号引用就可以转变为目标方法在类中方法表中的位置，从而使得方法被成功调用</strong></p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="4-过程三：初始化（Initilazation）阶段"><a href="#4-过程三：初始化（Initilazation）阶段" class="headerlink" title="4.过程三：初始化（Initilazation）阶段"></a>4.过程三：初始化（Initilazation）阶段</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%B6%E6%AE%B5%E6%A6%82%E8%BF%B0.png" alt="初始化阶段概述"></p>
<ol>
<li><p>static 与 final 的搭配问题</p>
<ul>
<li><p>使用 static+ final 修饰的字段的显式赋值的操作，到底是在哪个阶段进行的赋值？</p>
<ul>
<li>情况 1：<strong>在链接阶段的准备环节赋值</strong></li>
<li>情况 2：<strong>在初始化阶段 <code>&lt;clinit&gt;()</code> 中赋值</strong></li>
</ul>
</li>
<li><p>在链接阶段的准备环节赋值的情况：</p>
<ul>
<li>对于基本数据类型的字段来说，如果使用 static final 修饰，则显式赋值（直接赋值常量，而非调用方法）通常是在链接阶段的准备环节进行</li>
<li>对于 String 来说，如果使用字面量的方式赋值，使用 static final 修饰的话，则显式赋值通常是在链接阶段的准备环节进行</li>
<li>在初始化阶段  <code>&lt;clinit&gt;()</code>  中赋值的情况： 排除上述的在准备环节赋值的情况之外的情况</li>
</ul>
</li>
<li><p><strong>最终结论</strong></p>
<blockquote>
<p><strong>使用 <code>static+final</code> 修饰，且显式赋值中不涉及到方法或构造器调用的基本数据类型或 String 类型的显式赋值，是在链接阶段的准备环节进行</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在链接阶段的准备环节赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INT_CONSTANT</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="comment">// 在初始化阶段clinit&gt;()中赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NUM1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">10</span>);  </span><br><span class="line"><span class="comment">// 在初始化阶段&lt;clinit&gt;()中赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;  </span><br><span class="line"></span><br><span class="line"><span class="comment">// 在初始化阶段&lt;clinit&gt;()中赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">INTEGER_CONSTANT1</span> <span class="operator">=</span> Integer.valueOf(<span class="number">100</span>);</span><br><span class="line"><span class="comment">// 在初始化阶段&lt;clinit&gt;()中概值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">Integer</span> <span class="variable">INTEGER_CONSTANT2</span> <span class="operator">=</span> Integer.valueOf(<span class="number">100</span>);           </span><br><span class="line"></span><br><span class="line"><span class="comment">// 在链接阶段的准备环节赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">s0</span> <span class="operator">=</span> <span class="string">&quot;helloworld0&quot;</span>;      </span><br><span class="line"><span class="comment">// 在初始化阶段&lt;clinit&gt;()中赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;helloworld1&quot;</span>);</span><br><span class="line"><span class="comment">// 在初始化阶段&lt;clinit&gt;()中赋值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;hellowrold2&quot;</span>;                                  </span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p><code>&lt;clinit&gt;()</code> 的线程安全性</p>
<blockquote>
<p>对于 <code>&lt;clinit&gt;()</code> 方法的调用，也就是类的初始化，<strong>虚拟机会在内部确保其多线程环境中的安全性</strong></p>
<p>虚拟机会保证一个类的 <code>&lt;clinit&gt;()</code>  方法在多线程环境中被正确地加锁同步，如果多个线程同时去初始化一个类，那么只会有一个线程去执行这个类的 <code>&lt;clinit&gt;()</code>  方法，其他线程都需要阻塞等待，直到活动线程执行 <code>&lt;clinit&gt;()</code>  方法完毕</p>
<p>正是<strong>因为函数 <code>&lt;clinit&gt;()</code>  带锁线程安全的</strong>，因此如果在一个类的&lt;clinit&gt;()方法中有耗时很长的操作，就可能造成多个线程阻塞，引发死锁。并且这种死锁是很难发现的，因为看起来它们并没有可用的锁信息</p>
<p>如果之前的线程成功加载了类，则等在队列中的线程就没有机会再执行 <code>&lt;clinit&gt;()</code>  方法了</p>
<p>当需要使用这个类时，虚拟机会直接返回给它已经准备好的信息</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">StaticA</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Class.forName(<span class="string">&quot;StaticB&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;Static init OK&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StaticB</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Class.forName(<span class="string">&quot;StaticA&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;Static init OK&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;t1 准备加载 StaticA&quot;</span>);</span><br><span class="line">            <span class="type">StaticA</span> <span class="variable">A</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StaticA</span>();</span><br><span class="line">            System.out.println(<span class="string">&quot;t1 完成加载 StaticA&quot;</span>);</span><br><span class="line">        &#125;, <span class="string">&quot;t1&quot;</span>).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;t2 准备加载 StaticB&quot;</span>);</span><br><span class="line">            <span class="type">StaticB</span> <span class="variable">B</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StaticB</span>();</span><br><span class="line">            System.out.println(<span class="string">&quot;t2 完成加载 StaticB&quot;</span>);</span><br><span class="line">        &#125;, <span class="string">&quot;t2&quot;</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// output</span></span><br><span class="line">t1 准备加载 StaticA</span><br><span class="line">t2 准备加载 StaticB</span><br></pre></td></tr></table></figure>
</li>
<li><p>类的初始化情况：主动使用 VS 被动使用</p>
<blockquote>
<p>Java 程序对类的使用分为两种：主动使用和被动使用</p>
<p>主动使用会进行初始化阶段，即有调用 <code>&lt;clinit&gt;()</code></p>
<p>被动使用不会进行初始化阶段，即没有调用 <code>&lt;clinit&gt;()</code></p>
</blockquote>
<ul>
<li><p>主动使用</p>
<blockquote>
<p>Class 只有在必须要首次使用的时候才会被装载，Java 虚拟机不会无条件地装载 Class 类型</p>
<p>Java 虚拟机规定，一个类或接口在初次使用前，必须要进行初始化。这里指的使用，是指主动使用</p>
<p>主动使用只有下列几种情况：实例化、静态方法、静态字段、反射、继承、default 方法、main 方法、MethodHandle</p>
<p>（即：如果出现上述的情况，则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成）</p>
</blockquote>
<ul>
<li><p><strong>实例化</strong></p>
<blockquote>
<p>当创建一个类的实例时，比如使用 new 关键字，或者通过反射、克隆、反序列化</p>
</blockquote>
</li>
<li><p><strong>静态方法</strong></p>
<blockquote>
<p>当调用类的静态方法时，即当使用了字节码 <code>invokestatic</code> 指令</p>
</blockquote>
</li>
<li><p><strong>静态字段</strong></p>
<blockquote>
<p>当使用类、接口的静态字段时（final 修饰特殊考虑）</p>
<p>比如使用 <code>getstatic</code> 或者 <code>putstatic</code> 指令（对应访问变量、赋值变量操作）</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ActiveUse</span> &#123;</span><br><span class="line">	<span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(User.num);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;User类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>反射</strong></p>
<blockquote>
<p> 当使用 <code>java.lang.reflect</code> 包中的方法反射类的方法时</p>
<p>比如：<code>Class.forName(&quot;com.atguigu.java.Test&quot;)</code></p>
</blockquote>
</li>
<li><p><strong>继承</strong></p>
<p><span id="继承"></span></p>
<blockquote>
<p>当初始化子类时，如果发现其父类还没有进行过初始化，则需要先触发其父类的初始化</p>
<p>当 Java 虚拟机初始化一个类时，要求它的所有父类都已经被初始化，但是这条规则并不适用于接口</p>
<ul>
<li>在初始化一个类时，并不会先初始化它所实现的接口</li>
<li>在初始化一个接口时，并不会先初始化它的父接口</li>
<li><strong>因此一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时，才会导致该接口的初始化</strong></li>
</ul>
<p><a href="#%E4%BD%BF%E7%94%A8%E7%88%B6%E7%B1%BB%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F">注意对比 <code>使用父类静态变量</code></a></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PassiveUse</span> &#123;</span><br><span class="line"> 	<span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(Child.num);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Child类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Parent类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// output</span></span><br><span class="line">Parent类的初始化</span><br><span class="line">Child类的初始化</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>default 方法</strong></p>
<blockquote>
<p>如果一个<strong>接口</strong>定义了 default 方法（不管是否调用），那么<strong>直接实现或者间接实现该接口的类的初始化，该接口要在其之前被初始化</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> <span class="keyword">implements</span> <span class="title class_">Compare</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Parent类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Compare</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() &#123;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">// 输出该语句则说明进行了初始化</span></span><br><span class="line">            System.out.println(<span class="string">&quot;Compare接口的初始化&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;hello world&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(Parent.num);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// output</span></span><br><span class="line">Compare接口的初始化</span><br><span class="line">Parent类的初始化</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>main 方法</strong></p>
<blockquote>
<p>当虚拟机启动时，用户需要指定一个要执行的主类（包含 <code>main()</code> 方法的那个类），虚拟机会先初始化这个主类</p>
<p>JVM 启动的时候通过引导类加载器加载一个初始类</p>
<p>这个类在调用 <code>public static void main(String[])</code> 方法之前被链接和初始化</p>
<p>这个方法的执行将依次导致所需的类的加载，链接和初始化</p>
</blockquote>
</li>
<li><p><strong>MethodHandle</strong></p>
<blockquote>
<p>当初次调用 <code>MethodHandle</code> 实例时，初始化该 <code>MethodHandle</code> 指向的方法所在的类（涉及解析 <code>REF getStatic、REF_putStatic、REF invokeStatic</code> 方法句柄对应的类）</p>
</blockquote>
</li>
</ul>
</li>
<li><p>被动使用</p>
<blockquote>
<p>除了以上的情况属于主动使用，其他的情况均属于被动使用</p>
<p>被动使用不会引起类的初始化</p>
<p><strong>并不是在代码中出现的类，就一定会被加载或者初始化</strong></p>
<p>如果不符合主动使用的条件，类就不会初始化</p>
</blockquote>
<ul>
<li><p>使用父类静态变量</p>
<p><span id="使用父类静态变量"></span></p>
<blockquote>
<p>当通过子类引用父类的静态变量不会导致子类初始化，只有真正声明这个字段的类才会被初始化</p>
<p><a href="#%E7%BB%A7%E6%89%BF">注意对比 <code>继承</code></a></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PassiveUse</span> &#123;</span><br><span class="line"> 	<span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(Child.num);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Child类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Parent类的初始化&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// output</span></span><br><span class="line">Parent类的初始化</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>数组定义</p>
<blockquote>
<p>通过数组定义类引用，不会触发此类的初始化</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 此时不会初始化</span></span><br><span class="line">Parent[] parents= <span class="keyword">new</span> <span class="title class_">Parent</span>[<span class="number">10</span>];</span><br><span class="line">System.out.println(parents.getClass());</span><br><span class="line"><span class="comment">// new的话才会初始化</span></span><br><span class="line">parents[<span class="number">0</span>] = <span class="keyword">new</span> <span class="title class_">Parent</span>();</span><br></pre></td></tr></table></figure>
</li>
<li><p>引用常量</p>
<blockquote>
<p>引用常量不会触发此类或接口的初始化</p>
<p>因为常量在链接阶段就已经被显式赋值了</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PassiveUse</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 引用常量不会初始化</span></span><br><span class="line">        System.out.println(Serival.num);</span><br><span class="line">        <span class="comment">// 但引用其他类的话还是会初始化</span></span><br><span class="line">        System.out.println(Serival.num2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Serival</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() &#123;</span><br><span class="line">        &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Serival初始化&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">num2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">10</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>loadClass 方法</p>
<blockquote>
<p>调用 <code>ClassLoader</code> 类的 <code>loadClass()</code> 方法加载一个类并不是对类的主动使用，不会导致类的初始化</p>
<p>与 <code>Class.forName</code> 的区别： <code>Class.forName</code>  会触发该类的初始化，而 <code>loadClass</code> 不会</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Class</span> <span class="variable">clazz</span> <span class="operator">=</span> ClassLoader.getSystemClassLoader().loadClass(<span class="string">&quot;Person&quot;</span>);</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
</li>
<li><p>扩展</p>
<blockquote>
<p><code>-XX:+TraceClassLoading</code>：追踪打印类的加载信息</p>
</blockquote>
</li>
</ol>
<h4 id="5-过程四：类的使用（Using）"><a href="#5-过程四：类的使用（Using）" class="headerlink" title="5.过程四：类的使用（Using）"></a>5.过程四：类的使用（Using）</h4><blockquote>
<p>任何一个类型在使用之前都必须经历过完整的加载、链接和初始化 3 个类加载步骤</p>
<p>一旦一个类型成功经历过这 3 个步骤之后，便<code>万事俱备只欠东风</code>，就等着开发者使用了</p>
<p>开发人员可以在程序中访问和调用它的静态类成员信息（比如：静态字段、静态方法），或者使用 new 关键字为其创建对象实例</p>
</blockquote>
<h4 id="6-过程五：类的卸载（Unloading）"><a href="#6-过程五：类的卸载（Unloading）" class="headerlink" title="6.过程五：类的卸载（Unloading）"></a>6.过程五：类的卸载（Unloading）</h4><ol>
<li><p>类、类的加载器、类的实例之间的引用关系</p>
<blockquote>
<p><strong>在类加载器的内部实现中，用一个 Java 集合来存放所加载类的引用</strong></p>
<p>另一方面，<strong>一个 Class 对象总是会引用它的类加载器</strong>，调用 Class 对象的 <code>getClassLoader()</code> 方法，就能获得它的类加载器。由此可见，<strong>代表某个类的 Class 实例与其类的加载器之间为双向关联关系</strong></p>
<p><strong>一个类的实例总是引用代表这个类的 Class 对象</strong>。在 Object 类中定义了 <code>getClass()</code> 方法，这个方法返回代表对象所属类的 Class 对象的引用</p>
<p>此外，<strong>所有的 java 类都有一个静态属性 class，它引用代表这个类的 Class 对象</strong></p>
</blockquote>
</li>
<li><p>类的生命周期</p>
<blockquote>
<p>当 Sample 类被加载、链接和初始化后，它的生命周期就开始了</p>
<p>当代表 Sample 类的 Class 对象不再被引用，即不可触及时，Class 对象就会结束生命周期，Sample 类在方法区内的数据也会被卸载，从而结束 Sample 类的生命周期</p>
<p><strong>一个类何时结束生命周期，取决于代表它的 Class 对象何时结束生命周期</strong></p>
</blockquote>
</li>
<li><p>具体例子</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E7%9A%84%E5%8D%B8%E8%BD%BD%E4%B8%BE%E4%BE%8B.png" alt="类的卸载举例"></p>
<blockquote>
<p>loader1 变量和 obj 变量间接应用代表 Sample 类的 Class 对象，而 objClass 变量则直接引用它</p>
<p>如果程序运行过程中，将上图左侧三个引用变量都置为 null，此时 Sample 对象结束生命周期，MyClassLoader 对象结束生命周期，代表 Sample 类的 Class 对象也结束生命周期，Sample 类在方法区内的二进制数据被卸载</p>
<p>当再次有需要时，会检查 Sample 类的 Class 对象是否存在，如果存在会直接使用，不再重新加载；如果不存在 Sample 类会被重新加载，在 Java 虚拟机的堆区会生成一个新的代表 Sample 类的 Class 实例（可以通过哈希码查看是否是同一个实例）</p>
</blockquote>
</li>
<li><p>类的卸载</p>
<ul>
<li><p><strong>启动类加载器加载的类型在整个运行期间是不可能被卸载的（jvm 和 jls 规范）</strong></p>
</li>
<li><p><strong>被系统类加载器和扩展类加载器加载的类型在运行期间不太可能被卸载</strong>，因为系统类加载器实例或者扩展类的实例基本上在整个运行期间总能直接或者间接的访问的到，其达到 unreachable 的可能性极小</p>
</li>
<li><p>被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载，而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到</p>
<blockquote>
<p>可以预想，稍微复杂点的应用场景中（比如：很多时候用户在开发自定义类加载器实例的时候采用缓存的策略以提高系统性能），被加载的类型在运行期间也是几乎不太可能被卸载的（至少卸载的时间是不确定的）</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>综合以上三点，一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的</p>
<p>开发者在开发代码时候，不应该对虚拟机的类型卸载做任何假设的前提下，来实现系统中的特定功能</p>
</blockquote>
</li>
<li><p>回顾：方法区的垃圾回收</p>
<blockquote>
<p>方法区的垃圾收集主要回收两部分内容：常量池中废弃的常量和不再使用的类型</p>
<p>HotSpot 虚拟机对常量池的回收策略是很明确的，只要常量池中的常量没有被任何地方引用，就可以被回收</p>
<p>判定一个常量是否废弃还是相对简单，而要判定一个类型是否属于<code>不再使用的类</code>的条件就比较苛刻了</p>
<p>需要同时满足下面三个条件：</p>
<ul>
<li><strong>该类所有的实例都已经被回收。也就是Java堆中不存在该类及其任何派生子类的实例</strong></li>
<li><strong>加载该类的类加载器已经被回收</strong>（这个条件除非是经过精心设计的可替换类加载器的场景，如OSGi、JSP的重加载等，否则通常是很难达成的）</li>
<li>该类对应的 <code>java.lang.Class</code> 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法</li>
</ul>
<p>Java 虚拟机被允许对满足上述三个条件的无用类进行回收，这里说的仅仅是<code>被允许</code>，而并不是和对象一样，没有引用了就必然会回收</p>
</blockquote>
</li>
</ol>
<h3 id="4-再谈类的加载器"><a href="#4-再谈类的加载器" class="headerlink" title="4.再谈类的加载器"></a>4.再谈类的加载器</h3><h4 id="1-概述-2"><a href="#1-概述-2" class="headerlink" title="1.概述"></a>1.概述</h4><blockquote>
<p>类加载器是JVM执行类加载机制的前提</p>
<p>ClassLoader 是 Java 的核心组件，所有的 Class 都是由 ClassLoader 进行加载的，ClassLoader负责通过各种方式将 Class 信息的二进制数据流读入 JVM 内部，转换为一个与目标类对应的 <code>java.lang.Class</code> 对象实例，然后交给Java虚拟机进行链接、初始化等操作</p>
<p>ClassLoader 在整个装载阶段，只能影响到类的加载，而无法通过 ClassLoader 去改变类的链接和初始化行为</p>
<p>至于它是否可以运行，则由Execution Engine决定</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%A6%82%E8%BF%B0.png" alt="类加载器概述"></p>
<blockquote>
<p>类加载器最早出现在 Java1.0 版本中，那个时候只是单纯为了满足 Java Applet 应用而被研发出来</p>
<p>但如今类加载器却在 OSGi、字节码加解密领域大放异彩</p>
<p>这主要归功于 Java 虚拟机的设计者们当初在设计类加载器的时候，并没有考虑将它绑定在 JVM 内部，使得能够更加灵活和动态地执行类加载操作</p>
</blockquote>
<ol>
<li><p>大厂面试题</p>
<blockquote>
<p><strong>蚂蚁金服</strong></p>
<ul>
<li><p>深入分析ClassLoader，双亲委派机制</p>
</li>
<li><p>类加载器的双亲委派模型是什么？一面：双亲委派机制及使用原因</p>
</li>
</ul>
<p><strong>百度</strong></p>
<ul>
<li><p>都有哪些类加载器，这些类加载器都加载哪些文件？</p>
</li>
<li><p>手写一个类加载器 Demo</p>
</li>
<li><p><code>Class.forName(&quot;java.lang.String&quot;)</code> 和 <code>Class.getClassLoader().Loadclass(&quot;java.lang.String&quot;)</code> 有什么区别？</p>
</li>
</ul>
<p><strong>腾讯</strong></p>
<ul>
<li><p>什么是双亲委派模型？</p>
</li>
<li><p>类加载器有哪些？</p>
</li>
</ul>
<p><strong>小米</strong></p>
<ul>
<li>双亲委派模型介绍一下</li>
</ul>
<p><strong>滴滴</strong></p>
<ul>
<li>简单说说你了解的类加载器一面：讲一下双亲委派模型，以及其优点</li>
</ul>
<p><strong>字节跳动</strong></p>
<ul>
<li>什么是类加载器，类加载器有哪些？</li>
</ul>
<p><strong>京东</strong></p>
<ul>
<li><p>类加载器的双亲委派模型是什么？</p>
</li>
<li><p>双亲委派机制可以打破吗？为什么</p>
</li>
</ul>
</blockquote>
</li>
<li><p>类加载方式的分类</p>
<ul>
<li><p>显式加载 VS 隐式加载</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%88%86%E7%B1%BB.png" alt="类加载的分类"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//隐式加载</span></span><br><span class="line">User user=<span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line"><span class="comment">//显式加载，并初始化</span></span><br><span class="line">Class clazz=Class.forName(<span class="string">&quot;com.test.java.User&quot;</span>);</span><br><span class="line"><span class="comment">//显式加载，但不初始化</span></span><br><span class="line">ClassLoader.getSystemClassLoader().loadClass(<span class="string">&quot;com.test.java.Parent&quot;</span>); </span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>类加载器的必要性</p>
<blockquote>
<p>一般情况下，Java开发人员并不需要在程序中显式地使用类加载器，但是了解类加载器的加载机制却显得至关重要</p>
</blockquote>
<ul>
<li><p>避免在开发中遇到 <code>java.lang.ClassNotFoundException</code> 异常或 <code>java.lang.NoClassDefFoundError</code> 异常时，手足无措</p>
<blockquote>
<p>只有了解类加载器的加载机制才能够在出现异常的时候快速地根据错误异常日志定位问题和解决问题</p>
</blockquote>
</li>
<li><p>需要支持类的动态加载或需要对编译后的字节码文件进行加解密操作时，就需要与类加载器打交道了</p>
</li>
<li><p>开发人员可以在程序中编写自定义类加载器来重新定义类的加载规则，以便实现一些自定义的处理逻辑</p>
</li>
</ul>
</li>
<li><p>命名空间</p>
<ul>
<li><p><strong>何为类的唯一性？</strong></p>
<blockquote>
<p>对于任意一个类，都需要由加载它的类加载器和这个类本身一同确认其在 Java 虚拟机中的唯一性</p>
<p>每一个类加载器，都拥有一个独立的类名称空间</p>
<p><strong>比较两个类是否相等，只有在这两个类是由同一个类加载器加载的前提下才有意义</strong></p>
<p>否则，即使这两个类源自同一个Class文件，被同一个虚拟机加载，只要加载他们的类加载器不同，那这两个类就必定不相等</p>
<p><strong>总结：两个类相等需满足以下两个条件</strong></p>
<ul>
<li>两个类的全名相等</li>
<li>两个类由同一个类加载器加载</li>
</ul>
</blockquote>
</li>
<li><p><strong>命名空间</strong></p>
<ul>
<li><p>每个类加载器都有自己的命名空间，命名空间由该加载器及所有的父加载器所加载的类组成</p>
</li>
<li><p>在同一命名空间中，不会出现类的完整名字（包括类的包名）相同的两个类</p>
</li>
<li><p><strong>在不同的命名空间中，有可能会出现类的完整名字（包括类的包名）相同的两个类</strong></p>
</li>
</ul>
<blockquote>
<p>在大型应用中往往借助这一特性，来运行同一个类的不同版本</p>
</blockquote>
</li>
</ul>
</li>
<li><p>类加载机制的基本特征</p>
<blockquote>
<p>双亲委派模型（JDK1.2 开始使用）</p>
<p>但不是所有类加载都遵守这个模型，有时候启动类加载器所加载的类型，是可能要加载用户代码的，比如 JDK 内部的 <code>ServiceProvider/ServiceLoader</code> 机制，用户可以在标准API框架上，提供自己的实现</p>
<p>JDK也需要提供些默认的参考实现。例如，Java中 JNDI、JDBC、文件系统、Cipher 等很多方面，都是利用的这种机制，这种情况就不会用双亲委派模型去加载，而是利用所谓的上下文加载器</p>
</blockquote>
<ul>
<li><p>可见性</p>
<blockquote>
<p>子类加载器可以访问父加载器加载的类型，但是反过来是不允许的</p>
<p>不然因为缺少必要的隔离，我们就没有办法利用类加载器去实现容器的逻辑</p>
</blockquote>
</li>
<li><p>单一性</p>
<blockquote>
<p>由于父加载器的类型对于子加载器是可见的，所以父加载器中加载过的类型，就不会在子加载器中重复加载</p>
<p>但是注意，类加载器<code>邻居</code>间，同一类型仍然可以被加载多次，因为互相并不可见</p>
</blockquote>
</li>
</ul>
</li>
<li><p>类加载器之间的关系</p>
<blockquote>
<p>Launcher类核心代码</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Launcher.ExtClassLoader var1;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    var1 = Launcher.ExtClassLoader.getExtClassLoader();</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException var10) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(<span class="string">&quot;Could not create extension class loader&quot;</span>, var10);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.loader = Launcher.AppClassLoader.getAppClassLoader(var1);</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException var9) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(<span class="string">&quot;Could not create application class loader&quot;</span>, var9);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Thread.currentThread().setContextClassLoader(<span class="built_in">this</span>.loader);</span><br></pre></td></tr></table></figure>

<ul>
<li><p><strong>ExtClassLoader的Parent类是null</strong></p>
</li>
<li><p><strong>AppClassLoader的Parent类是ExtClassLoader</strong></p>
</li>
<li><p><strong>当前线程的ClassLoader是AppClassLoader</strong></p>
</li>
</ul>
<blockquote>
<p><strong>注意，这里的 Parent 类并不是 Java 语言意义上的继承关系，而是一种包含关系</strong></p>
</blockquote>
</li>
</ol>
<h4 id="2-类的加载器分类"><a href="#2-类的加载器分类" class="headerlink" title="2.类的加载器分类"></a>2.类的加载器分类</h4><blockquote>
<p>VM支持两种类型的类加载器，分别为<strong>引导类加载器（Bootstrap ClassLoader）</strong>和<strong>自定义类加载器（User-Defined ClassLoader）</strong></p>
<p>从概念上来讲，自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器，但是Java虚拟机规范却没有这么定义，而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器</p>
<p>无论类加载器的类型如何划分，在程序中我们最常见的类加载器结构主要是如下情况：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E7%9A%84%E5%88%86%E7%B1%BB.png" alt="类加载器的分类"></p>
<p>启动类加载器通过 C&#x2F;C++ 语言编写，而自定义类加载器都是由 Java 语言编写的（扩展类加载器和应用程序类加载器是由 JDK 开发人员使用 java 语言来实现的）所以也被称为自定义类加载器</p>
<p>除了顶层的启动类加载器外，其余的类加载器都应当有自己的<code>父类</code>加戟器</p>
<p><strong>不同类加载器看似是继承（Inheritance）关系，实际上是包含关系。在下层加载器中，包含着上层加载器的引用</strong></p>
<p>正是由于子类加载器中包含着父类加载器的引用，所以可以通过子类加载器的方法获取对应的父类加载器</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ClassLoader</span>&#123;</span><br><span class="line">    ClassLoader parent;<span class="comment">//父类加载器</span></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">ClassLoader</span><span class="params">(ClassLoader parent)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.parent = parent;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ParentClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ParentClassLoader</span><span class="params">(ClassLoader parent)</span>&#123;</span><br><span class="line">        <span class="built_in">super</span>(parent);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ChildClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ChildClassLoader</span><span class="params">(ClassLoader parent)</span>&#123; <span class="comment">//parent = new ParentClassLoader();</span></span><br><span class="line">        <span class="built_in">super</span>(parent);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ol>
<li><p>引导类加载器（启动类加载器 Bootstrap ClassLoader）</p>
<ul>
<li><p>这个类加载使用 C&#x2F;C++ 语言实现的，嵌套在 JVM 内部</p>
</li>
<li><p>它用来加载 Java 的核心库（ <code>JAVAHOME/jre/lib/rt.jar</code> 或 <code>sun.boot.class.path</code> 路径下的内容）。用于提供JVM自身需要的类</p>
</li>
<li><p>并不继承自 <code>java.lang.ClassLoader</code>，没有父加载器</p>
</li>
<li><p>出于安全考虑，Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类</p>
</li>
<li><p>加载扩展类和应用程序类加载器，并指定为他们的父类加载器</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(<span class="string">&quot;＊＊＊＊＊＊＊＊＊＊启动类加载器＊＊＊＊＊＊＊＊＊＊&quot;</span>);</span><br><span class="line"><span class="comment">// 获取BootstrapclassLoader能够加载的api的路径</span></span><br><span class="line">URL[] urLs = sun.misc.Launcher.getBootstrapcLassPath().getURLs();</span><br><span class="line"><span class="keyword">for</span> (URL element : urLs) &#123;</span><br><span class="line">    System.out.println(element.toExternalForm());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 从上面的路径中随意选择一个类，来看看他的类加载器是什么：引导类加载器</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> java.security.Provider.class.getClassLoader();</span><br><span class="line">System.out.println(classLoader);</span><br></pre></td></tr></table></figure>

<blockquote>
<p>输出结果</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%BC%95%E5%AF%BC%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8output.png" alt="引导类加载器output"></p>
</li>
<li><p>扩展类加载器（Extension ClassLoader）</p>
<ul>
<li><p>Java语言编写，由 <code>sun.misc.Launcher$ExtClassLoader</code> 实现</p>
</li>
<li><p>继承于 ClassLoader 类</p>
</li>
<li><p>父类加载器为启动类加载器</p>
</li>
<li><p>从 <code>java.ext.dirs</code> 系统属性所指定的目录中加载类库，或从 JDK 的安装目录的 <code>jre/lib/ext</code> 子目录下加载类库。如果用户创建的JAR放在此目录下，也会自动由扩展类加载器加载</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(<span class="string">&quot;＊＊＊＊＊＊＊＊＊＊＊扩展类加载器＊＊＊＊＊＊＊＊＊＊＊&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">extDirs</span> <span class="operator">=</span>System.getProperty(<span class="string">&quot;java.ext.dirs&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (String path :extDirs.split( regex:<span class="string">&quot;;&quot;</span>))&#123;</span><br><span class="line">    System.out.println(path);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从上面的路径中随意选择一个类，来看看他的类加载器是什么：扩展类加载器</span></span><br><span class="line"><span class="type">lassLoader</span> <span class="variable">classLoader1</span> <span class="operator">=</span> sun.security.ec.CurveDB.class.getClassLoader();</span><br><span class="line">System.out.print1n(classLoader1); <span class="comment">//sun.misc. Launcher$ExtCLassLoader@1540e19d</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>执行结果</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%89%A9%E5%B1%95%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8output.png" alt="扩展类加载器output"></p>
</li>
<li><p>系统类加载器（AppClassLoader）</p>
<ul>
<li>java语言编写，由 <code>sun.misc.Launcher$AppClassLoader</code> 实现</li>
<li>继承于 ClassLoader 类</li>
<li>父类加载器为扩展类加载器</li>
<li>它负责加载环境变量 classpath 或系统属性 <code>java.class.path</code> 指定路径下的类库</li>
<li>应用程序中的类加载器默认是系统类加载器</li>
<li>它是用户自定义类加载器的默认父加载器</li>
<li>通过 ClassLoader 的 <code>getSystemClassLoader()</code> 方法可以获取到该类加载器</li>
</ul>
</li>
<li><p>用户自定义类加载器</p>
<ul>
<li><p>在Java的日常应用程序开发中，类的加载几乎是由上述3种类加载器相互配合执行的。在必要时，我们还可以自定义类加载器，来定制类的加载方式</p>
</li>
<li><p>体现 Java 语言强大生命力和巨大魅力的关键因素之一便是，Java开发者可以自定义类加载器来实现类库的动态加载，加载源可以是本地的 JAR 包，也可以是网络上的远程资源</p>
</li>
<li><p><strong>通过类加载器可以实现非常绝妙的插件机制</strong>，这方面的实际应用案例举不胜举</p>
<blockquote>
<p>例如，著名的 OSGi 组件框架、 Eclipse 的插件机制</p>
<p>类加载器为应用程序提供了一种动态增加新功能的机制，这种机制无须重新打包发布应用程序就能实现</p>
</blockquote>
</li>
<li><p><strong>自定义加载器能够实现应用隔离</strong></p>
<blockquote>
<p>例如 Tomcat，Spring 等中间件和组件框架都在内部实现了自定义的加载器，并通过自定义加载器隔离不同的组件模块</p>
<p>这种机制比 C&#x2F;C++ 程序要好太多，想不修改 C&#x2F;C++ 程序就能为其新增功能，几乎是不可能的，仅仅一个兼容性便能阻挡住所有美好的设想</p>
</blockquote>
</li>
<li><p>自定义类加载器通常需要继承于 ClassLoader</p>
</li>
</ul>
</li>
</ol>
<h4 id="3-测试不同的类的加载器"><a href="#3-测试不同的类的加载器" class="headerlink" title="3.测试不同的类的加载器"></a>3.测试不同的类的加载器</h4><blockquote>
<p>每个 Class 对象都会包含一个定义它的 ClassLoader 的一个引用</p>
</blockquote>
<ul>
<li><p>获取 ClassLoader 的途径</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获得当前类的ClassLoader</span></span><br><span class="line">clazz.getClassLoader()</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 获得当前线程上下文的ClassLoader</span></span><br><span class="line">Thread.currentThread().getContextClassLoader()</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 获得系统的ClassLoader</span></span><br><span class="line">ClassLoader.getSystemClassLoader()</span><br></pre></td></tr></table></figure>
</li>
<li><p>说明</p>
<blockquote>
<p>站在程序的角度看，引导类加载器与另外两种类加载器（系统类加载器和扩展类加载器）并不是同一个层次意义上的加载器，引导类加载器是使用 C++ 语言编写而成的，而另外两种类加载器则是使用Java语言编写而成的</p>
<p>由于引导类加载器压根儿就不是一个 Java 类，因此在 Java 程序中只能打印出空值</p>
<p>数组类的 Class 对象，不是由类加载器去创建的，而是在 Java 运行期 JVM 根据需要自动创建的</p>
<p>对于数组类的类加载器来说，是通过 <code>Class.getClassLoader()</code> 返回的，与数组当中元素类型的类加载器是一样的</p>
<p><strong>如果数组当中的元素类型是基本数据类型，数组类是没有类加载器的</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 运行结果：null</span></span><br><span class="line">String[] strArr = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">6</span>];</span><br><span class="line">System.out.println(strArr.getClass().getClassLoader());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 运行结果：sun.misc.Launcher＄AppCLassLoader＠18b4aac2</span></span><br><span class="line">ClassLoaderTest[] test=<span class="keyword">new</span> <span class="title class_">ClassLoaderTest</span>[<span class="number">1</span>];</span><br><span class="line">System.out.println(test.getClass().getClassLoader());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 运行结果：null</span></span><br><span class="line"><span class="type">int</span>[]ints =<span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">2</span>];</span><br><span class="line">System.out.println(ints.getClass().getClassLoader());</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//获取系统该类加载器</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">systemClassLoader</span> <span class="operator">=</span> ClassLoader.getSystemClassLoader();</span><br><span class="line">System.out.println(systemClassLoader);<span class="comment">//sun.misc.Launcher$AppCLassLoader@18b4aac2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//获取扩展类加载器</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">extClassLoader</span> <span class="operator">=</span> systemClassLoader.getParent();</span><br><span class="line">System.out.println(extClassLoader);<span class="comment">//sun.misc. Launcher$ExtCLassLoader@1540e19d</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//试图获取引导类加载器：失败</span></span><br><span class="line"><span class="type">ClassLoader</span> <span class="variable">bootstrapClassLoader</span> <span class="operator">=</span> extClassLoader.getParent();</span><br><span class="line">System.out.println(bootstrapClassLoader);<span class="comment">//null</span></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;java.lang.String&quot;</span>).getClassLoader();</span><br><span class="line">    System.out.println(classLoader);<span class="comment">// null</span></span><br><span class="line">    <span class="comment">//自定义的类默认使用系统类加载器</span></span><br><span class="line">    ClassLoader classLoader1=Class.forName(<span class="string">&quot;Test&quot;</span>).getClassLoader();</span><br><span class="line">    System.out.println(classLoader1);<span class="comment">// sun.misc.Launcher$AppClassLoader@18b4aac2</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//关于数组类型的加载：使用的类的加载器与数组元素的类的加载器相同</span></span><br><span class="line">    String[] arrstr = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">10</span>];</span><br><span class="line">    System.out.println(arrstr.getClass().getClassLoader());<span class="comment">//null：表示使用的是引导类加载器</span></span><br><span class="line"></span><br><span class="line">    Test[] arr1 =<span class="keyword">new</span> <span class="title class_">Test</span>[<span class="number">10</span>];</span><br><span class="line">    System.out.println(arr1.getClass().getClassLoader());<span class="comment">//sun.misc. Launcher$AppcLassLoader@18b4aac2</span></span><br><span class="line"></span><br><span class="line">    <span class="type">int</span>[] arr2 = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">10</span>];</span><br><span class="line">    System.out.println(arr2.getClass().getClassLoader());<span class="comment">//null:</span></span><br><span class="line">&#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<h4 id="4-ClassLoader-源码解析"><a href="#4-ClassLoader-源码解析" class="headerlink" title="4.ClassLoader 源码解析"></a>4.ClassLoader 源码解析</h4><blockquote>
<p><strong>ClassLoader与现有类的关系：</strong></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/ClassLoader%E4%B8%8E%E7%8E%B0%E6%9C%89%E7%B1%BB%E7%9A%84%E5%85%B3%E7%B3%BB.png" alt="ClassLoader与现有类的关系"></p>
<p>除了以上虚拟机自带的加载器外，用户还可以定制自己的类加载器</p>
<p>Java提供了抽象类 <code>java.lang.ClassLoader</code>，所有用户自定义的类加载器都应该继承 ClassLoader 类</p>
</blockquote>
<ol>
<li><p>ClassLoader 的主要方法</p>
<ul>
<li><p>抽象类ClassLoader的主要方法：（内部没有抽象方法）</p>
<ul>
<li><p><code>public final ClassLoader getParent()</code></p>
<blockquote>
<p>返回该类加载器的超类加载器</p>
</blockquote>
</li>
<li><p><code>public Class&lt;?&gt; loadClass(String name) throws ClassNotFoundException</code></p>
<blockquote>
<p>加载名称为name的类，返回结果为 <code>java.lang.Class</code> 类的实例</p>
<p>如果找不到类，则返回 <code>ClassNotFoundException</code> 异常</p>
<p>该方法中的逻辑就是双亲委派模式的实现</p>
</blockquote>
</li>
<li><p><code>protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException</code></p>
<blockquote>
<p>查找二进制名称为 name 的类，返回结果为 <code>java.lang.Class</code> 类的实例</p>
<p>这是一个受保护的方法，JVM鼓励我们重写此方法，需要自定义加载器遵循双亲委托机制，该方法会在检查完父类加载器之后被 <code>loadClass()</code> 方法调用</p>
</blockquote>
<blockquote>
<p>在 JDK1.2 之前，在自定义类加载时，总会去继承 ClassLoader 类并重写 loadClass 方法，从而实现自定义的类加载类</p>
<p>但是在 JDK1.2 之后已不再建议用户去覆盖 loadClass() 方法，而是建议把自定义的类加载逻辑写在 findClass() 方法中</p>
<p>从前面的分析可知，findClass() 方法是在 loadClass() 方法中被调用的，当 loadClass() 方法中父加载器加载失败后，则会调用自己的 findClass() 方法来完成类加载，这样就可以保证自定义的类加载器也符合双亲委托模式</p>
<p>需要注意的是 ClassLoader 类中并没有实现 findClass() 方法的具体代码逻辑，取而代之的是抛出 ClassNotFoundException 异常，同时应该知道的是 findClass 方法通常是和 defineClass 方法一起使用的</p>
<p><strong>一般情况下，在自定义类加载器时，会直接覆盖 ClassLoader 的 findClass() 方法并编写加载规则，取得要加载类的字节码后转换成流，然后调用 defineClass() 方法生成类的 Class 对象</strong></p>
</blockquote>
</li>
<li><p><code>protected final Class&lt;?&gt; defineClass(String name, byte[] b,int off,int len)</code></p>
<blockquote>
<p>根据给定的字节数组 b 转换为 Class 的实例，off 和 len 参数表示实际 Class 信息在 byte 数组中的位置和长度，其中 byte 数组 b 是 ClassLoader 从外部获取的</p>
<p>这是受保护的方法，只有在自定义 ClassLoader 子类中可以使用</p>
</blockquote>
<blockquote>
<p>defineClass() 方法是用来将 byte 字节流解析成 JVM 能够识别的 Class 对象（ClassLoader 中已实现该方法逻辑），通过这个方法不仅能够通过 class 文件实例化 class 对象，也可以通过其他方式实例化 class 对象，如通过网络接收一个类的字节码，然后转换为byte字节流创建对应的Class 对象</p>
<p><strong>defineClass() 方法通常与 findClass() 方法一起使用，一般情况下在自定义类加载器时，会直接覆盖 ClassLoader 的 findClass() 方法并编写加载规则，取得要加载类的字节码后转换成流，然后调用 defineClass() 方法生成类的 Class 对象</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Class&lt;?&gt; findClass(String name) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">    <span class="comment">// 获取类的字节数组</span></span><br><span class="line">    <span class="type">byte</span>[] classData = getClassData(name);</span><br><span class="line">    <span class="keyword">if</span> (classData == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="comment">//使用 defineClass 生成 class 对象</span></span><br><span class="line">        <span class="keyword">return</span> defineClass(name,classData,θ,classData.length);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><code>protected final void resolveClass(Class&lt;?&gt; c)</code></p>
<blockquote>
<p>链接指定的一个 Java 类</p>
<p>使用该方法可以使用类的 Class 对象创建完成的同时也被解析</p>
<p>前面我们说链接阶段主要是对字节码进行验证，为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用</p>
</blockquote>
</li>
<li><p><code>protected final Class&lt;?&gt; findLoadedClass(String name)</code></p>
<blockquote>
<p>查找名称为 name 的已经被加载过的类，返回结果为 java.lang.Class 类的实例</p>
<p>这个方法是 final 方法，无法被修改</p>
</blockquote>
</li>
<li><p><code>private final ClassLoader parent;</code></p>
<blockquote>
<p>是一个 ClassLoader 的实例，这个字段所表示的 ClassLoader 也称为这个 ClassLoader 的双亲</p>
<p>在类加载的过程中，ClassLoader 可能会将某些请求交予自己的双亲处理</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>SecureClassLoader 与 URLClassLoader</p>
<blockquote>
<p>SecureClassLoader 扩展了 ClassLoader，新增了几个与使用相关的代码源（对代码源的位置及其证书的验证）和权限定义类验证（主要指对class源码的访问权限）的方法，一般我们不会直接跟这个类打交道，更多是与它的子类 URLClassLoader 有所关联</p>
<p>ClassLoader 是一个抽象类，很多方法是空的没有实现，比如 findClass()、findResource() 等</p>
<p>而 URLClassLoader 这个实现类为这些方法提供了具体的实现</p>
<p>并新增了 URLClassPath 类协助取得Class字节码流等功能</p>
<p><strong>在编写自定义类加载器时，如果没有太过于复杂的需求，可以直接继承 URLClassLoader 类</strong>，这样就可以避免自己去编写 findClass() 方法及其获取字节码流的方式，使自定义类加载器编写更加简洁</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/URLClassLoader.png" alt="URLClassLoader"></p>
</li>
<li><p>ExtClassLoader 与 AppClassLoader</p>
<blockquote>
<p>拓展类加载器bExtClassLoaderb和系统类加载器 AppClassLoader，都继承自URLClassLoader，是sun.misc.Launcher 的静态内部类</p>
<p>sun.misc.Launcher 主要被系统用于启动主应用程序，ExtClassLoader 和 AppClassLoader 都是由sun.misc.Launcher 创建的，其类主要类结构如下：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/ExtClassLoader%E4%B8%8EAppClassLoader.png" alt="ExtClassLoader与AppClassLoader"></p>
<blockquote>
<p><strong>ExtClassLoader 并没有重写 loadClass() 方法，这足矣说明其遵循双亲委派模式</strong></p>
<p><strong>AppClassLoader 重载了 loadClass() 方法，但最终调用的还是父类 loadClass() 方法，因此依然遵守双亲委派模式</strong></p>
</blockquote>
</li>
<li><p>Class.forName() 与 ClassLoader.loadClass()</p>
<ul>
<li><p>Class.forName()</p>
<blockquote>
<p>是一个静态方法，最常用的是 <code>Class.forName(String className);</code></p>
<p>根据传入的类的全限定名返回一个 Class 对象。该方法在将 Class 文件加载到内存的同时，<strong>会执行类的初始化</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Class.forName(<span class="string">&quot;com.atguigu.java.Helloworld&quot;</span>);</span><br></pre></td></tr></table></figure>
</li>
<li><p>ClassLoader.loadClass()</p>
<blockquote>
<p>是一个实例方法，需要一个 ClassLoader 对象来调用该方法</p>
<p><strong>该方法将Class文件加载到内存时，并不会执行类的初始化</strong>，直到这个类第一次使用时才进行初始化</p>
<p>该方法因为需要得到一个 ClassLoader 对象，所以可以根据需要指定使用哪个类加载器</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Classloader</span> <span class="variable">cl</span> <span class="operator">=</span> ......;</span><br><span class="line">cl.loadClass(<span class="string">&quot;com.atguigu.java.Helloworld&quot;</span>);</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ol>
<h4 id="5-双亲委派模型"><a href="#5-双亲委派模型" class="headerlink" title="5.双亲委派模型"></a>5.双亲委派模型</h4><ol>
<li><p>定义与本质</p>
<blockquote>
<p><strong>类加载器用来把类加载到 Java 虚拟机中</strong></p>
<p>从 JDK1.2 版本开始，类的加载过程采用双亲委派机制，这种机制能更好地保证 Java 平台的安全</p>
</blockquote>
<ul>
<li><p>定义</p>
<blockquote>
<p>如果一个类加载器在接到加载类的请求时，它首先不会自己尝试去加载这个类，而是把这个请求任务委托给父类加载器去完成，依次递归，如果父类加载器可以完成类加载任务，就成功返回</p>
<p>只有父类加载器无法完成此加载任务时，才自己去加载</p>
</blockquote>
</li>
<li><p>本质</p>
<blockquote>
<p>规定了类加载的顺序是：引导类加载器先加载，若加载不到，由扩展类加载器加载，若还加载不到，才会由系统类加载器或自定义的类加载器进行加载</p>
</blockquote>
</li>
</ul>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B61.png" alt="双亲委派机制1"></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B62.png" alt="双亲委派机制2"></p>
</li>
<li><p>优势与劣势</p>
<ul>
<li><p>双亲委派机制的优势</p>
<ul>
<li><p>避免类的重复加载，确保一个类的全局唯一性</p>
<blockquote>
<p>Java 类随着它的类加载器一起具备了一种带有优先级的层次关系，通过这种层级关可以避免类的重复加载，当父亲已经加载了该类时，就没有必要子 ClassLoader 再加载一次</p>
</blockquote>
</li>
<li><p>保护程序安全，防止核心API被随意篡改</p>
</li>
</ul>
</li>
<li><p>代码支持</p>
<blockquote>
<p>双亲委派机制在 <code>java.lang.ClassLoader.loadClass(String，boolean)</code> 接口中体现</p>
<p>该接口的逻辑如下：</p>
</blockquote>
<ul>
<li>先在当前加载器的缓存中查找有无目标类，如果有则直接返回</li>
<li>判断当前加载器的父加载器是否为空，如果不为空，则调用 <code>parent.loadClass(name，false)</code> 接口进行加载</li>
<li>反之如果当前加载器的父类加载器为空，则调用 <code>findBootstrapClassorNull(name)</code> 接口，让引导类加载器进行加载</li>
<li>如果通过以上 3 条路径都没能成功加载，则调用 f<code>indClass(name)</code> 接口进行加载。该接口最终会调用 <code>java.lang.ClassLoader</code> 接口的 <code>defineClass</code> 系列的 <code>native</code> 接口加载目标 Java 类</li>
</ul>
<blockquote>
<p>双亲委派的模型就体现在步骤 2、3 中</p>
</blockquote>
</li>
<li><p>双亲委派举例</p>
<blockquote>
<p>假设当前加载的是 <code>java.lang.Object</code> 这个类</p>
<p>该类属于 JDK 中核心得不能再核心的一个类，因此一定只能由引导类加载器进行加载</p>
<p>当 ]VM 准备加载 javaJang.Object 时，JVM 默认会使用系统类加载器去加载</p>
<p>按照上面 4 步加载的逻辑</p>
<ul>
<li>在第 1 步从系统类的缓存中肯定查找不到该类，于是进入第 2 步</li>
<li>由于从系统类加载器的父加载器是扩展类加载器，于是扩展类加载器继续从第 1 步开始重复</li>
<li>由于扩展类加载器的缓存中也一定查找不到该类，因此进入第2步</li>
<li>扩展类的父加载器是 null，因此系统调用 findClass（String），最终通过引导类加载器进行加载</li>
</ul>
</blockquote>
</li>
<li><p>思考</p>
<blockquote>
<p>如果在自定义的类加载器中重写 <code>java.lang.ClassLoader.loadClass(String)</code> 或<code>java.lang.ClassLoader.loadclass(String，boolean)</code> 方法，抹去其中的双亲委派机制，仅保留上面这 4  步中的第 1 步与第 4 步，那么是不是就能够加载核心类库了呢？</p>
<p>这也不行！</p>
<p>因为JDK还为核心类库提供了一层保护机制</p>
<p>不管是自定义的类加载器，还是系统类加载器抑或扩展类加载器，最终都必须调用 <code>java.lang.ClassLoader.defineclass(String，byte[]，int，int，ProtectionDomain)</code> 方法，而该方法会执行 <code>preDefineClass()</code> 接口，该接口中提供了对 JDK 核心类库的保护</p>
</blockquote>
</li>
<li><p>双亲委派机制的弊端</p>
<blockquote>
<p>检查类是否加载的委托过程是单向的，这个方式虽然从结构上说比较清晰，使各个ClassLoader的职责非常明确，但是同时会带来一个问题，即顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类</p>
<p>通常情况下，启动类加载器中的类为系统核心类，包括一些重要的系统接口，而在应用类加载器中，为应用类</p>
<p>按照这种模式，应用类访问系统类自然是没有问题，但是系统类访问应用类就会出现问题</p>
<p>比如<strong>在系统类中提供了一个接口，该接口需要在应用类中得以实现，该接口还绑定一个工厂方法，用于创建该接口的实例，而接口和工厂方法都在启动类加载器中</strong></p>
<p><strong>这时，就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题</strong></p>
</blockquote>
</li>
<li><p>结论</p>
<blockquote>
<p><strong>由于 Java 虚拟机规范并没有明确要求类加载器的加载机制一定要使用双亲委派模型，只是建议采用这种方式而已</strong></p>
<p>比如在 Tomcat 中，类加载器所采用的加载机制就和传统的双亲委派模型有一定区别，当缺省的类加载器接收到一个类的加载任务时，首先会由它自行加载，当它加载失败时，才会将类的加载任务委派给它的超类加载器去执行，这同时也是 Serylet 规范推荐的一种做法</p>
</blockquote>
</li>
</ul>
</li>
<li><p>破坏双亲委派机制</p>
<blockquote>
<p>双亲委派模型并不是一个具有强制性约束的模型，而是Java设计者推荐给开发者们的类加载器实现方式</p>
<p>在 Java 的世界中大部分的类加载器都遵循这个模型，但也有例外的情况，直到 Java 模块化出现为止，双亲委派模型主要出现过3次较大规模 <code>被破坏</code> 的情况</p>
</blockquote>
<ul>
<li><p>第一次破坏双亲委派机制</p>
<blockquote>
<p>双亲委派模型的第一次被破坏其实发生在双亲委派模型出现之前——即 JDK1.2 面世以前的远古时代</p>
<p>由于双亲委派模型在 JDK 1.2 之后才被引入，但是类加载器的概念和抽象类 <code>java.lang.ClassLoader</code> 则在 Java 的第一个版本中就已经存在，面对经存在的用户自定义类加载器的代码，Java 设计者们引入双亲委派模型时不得不做出一些妥协</p>
<p><strong>为了兼容这些已有代码，无法再以技术手段避免 loadClass() 被子类覆盖的可能性</strong>，只能在之后的java.lang.ClassLoader 中添加一个新的 protected 方法 findClass()，并引导用户编写的类加载逻辑时尽可能去重写这个方法，而不是在 loadClass() 中编写代码</p>
<p>上节我们已经分析过 loadClass() 方法，双亲委派的具体逻辑就实现在这里面，按照 loadClass() 方法的逻辑，如果父类加载失败，会自动调用自己的 findClass() 方法来完成加载，这样既不影响用户按照自己的意愿去加载类，又可以保证新写出来的类加载器是符合双亲委派规则的</p>
</blockquote>
</li>
<li><p>第二次破坏双亲委派机制：线程上下文类加载器（解决双亲委派弊端）</p>
<blockquote>
<p>双亲委派模型的第二次被破坏是由这个模型自身的缺陷导致的</p>
<p>双亲委派很好地解决了各个类加载器协作时基础类型的一致性问题<strong>（越基础的类由越上层的加载器进行加载）</strong>，基础类型之所以被称为基础，是因为它们总是作为被用户代码继承、调用的API存在，但程序设计往往没有绝对不变的完美规则，如果有基础类型又要调用回用户的代码，那该怎么办呢？</p>
<p>这并非是不可能出现的事情，一个典型的例子便是 JNDI 服务，JNDI 现在已经是 Java 的标准服务，它的代码由启动类加载器来完成加载（在 JDK 1.3 时加入到 rt.jar 的），肯定属于Java中很基础的类型了</p>
<p>但 JNDI 存在的目的就是对资源进行查找和集中管理，它需要调用由其他厂商实现并部署在应用程序的 ClassPath下的 JNDI 服务提供者接口（Service Provider Interface，SPI）的代码，现在问题来了，<strong>启动类加载器是绝不可能认识、加载这些代码的，那该怎么办？</strong>（SPI：在Java平台中，通常把核心类rt.jar中提供外部服务、可由应用层自行实现的接口称为SPI）</p>
</blockquote>
<blockquote>
<p>为了解决这个困境，Java的设计团队只好引入了一个不太优雅的设计：<strong>线程上下文类加载器（Thread Context ClassLoader）</strong></p>
<p>这个类加载器可以通过 java.lang.Thread 类的 setContextClassLoader() 方法进行设置，如果创建线程时还未设置，它将会从父线程中继承一个，如果在应用程序的全局范围内都没有设置过的话，那这个类加载器默认就是应用程序类加载器</p>
<p>有了线程上下文类加载器，程序就可以做一些舞弊的事情了</p>
<p>JNDI服务使用这个线程上下文类加载器去加载所需的 SPI 服务代码，<strong>这是一种父类加载器去请求子类加载器完成类加载的行为，这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器，已经违背了双亲委派模型的一般性原则</strong>，但也是无可奈何的事情</p>
<p>例如 JNDI、JDBC、JCE、JAXB 和 JBI 等。不过，当 SPI 的服务提供者多于一个的时候，代码就只能根据具体提供者的类型来硬编码判断，为了消除这种极不优雅的实现方式，在 JDK6 时，JDK 提供了java.util.ServiceLoader 类，以 META-INF&#x2F;services 中的配置信息，辅以责任链模式，这才算是给SPI的加载提供了一种相对合理的解决方案</p>
<p>默认上下文加载器就是应用类加载器，这样以上下文加载器为中介，使得启动类加载器中的代码也可以访问应用类加载器中的类</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%8A%A0%E8%BD%BD%E5%99%A8.png" alt="上下文加载器"></p>
</li>
<li><p>第三次破坏双亲委派机制</p>
<blockquote>
<p>双亲委派模型的第三次被破坏是由于用户对程序动态性的追求而导致的</p>
<p>如：**代码热替换(Hot Swap)、模块热部署(Hot Deployment)**等</p>
<p>IBM 公司主导的 JSR-291（即OSGiR4.2）实现模块化热部署的关键是它自定义的类加载器机制的实现，每一个程序模块（OSGi中称为Bundle）都有一个自己的类加载器，当需要更换一个 Bundle 时，就把 Bundle 连同类加载器一起换掉以实现代码的热替换</p>
<p>在 OSGi 环境下，<strong>类加载器不再双亲委派模型推荐的树状结构，而是进一步发展为更加复杂的网状结构</strong></p>
<p>当收到类加载请求时，OSGi 将按照下面的顺序进行类搜索：</p>
<ul>
<li><strong>将以 <code>java.*</code> 开头的类，委派给父类加载器加载</strong></li>
<li>否则，将委派列表名单内的类，委派给父类加载器加载</li>
<li>否则，将Import列表中的类，委派给 Export 这个类的 Bundle 的类加载器加载</li>
<li>否则，查找当前 Bundle 的 ClassPath，使用自己的类加载器加载</li>
<li>否则，查找类是否在自己的 Fragment Bundle 中，如果在，则委派给 Fragment Bundle 的类加载器加载</li>
<li>否则，查找 Dynamic Import 列表的 Bundle，委派给对应 Bundle 的类加载器加载</li>
<li>否则，类查找失败</li>
</ul>
</blockquote>
<blockquote>
<p>说明：只有开头两点仍然符合双亲委派模型的原则，其余的类查找都是在平级的类加载器中进行的</p>
<p>小结：这里，被破坏这个词来形容上述不符合双亲委派模型原则的行为，并不一定是带有贬义的。只要有明确的目的和充分的理由，突破旧有原则无疑是一种创新</p>
<p>正如：OSGi 中的类加载器的设计不符合传统的双亲委派的类加载器架构，且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议，但对这方面有了解的技术人员基本还是能达成一个共识，认为 <strong>OSGi 中对类加载器的运用是值得学习的，完全弄懂了 OSGi 的实现，就算是掌握了类加载器的精粹</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>热替换的实现</p>
<blockquote>
<p>热替换是指在程序的运行过程中，不停止服务，只通过替换程序文件来修改程序的行为</p>
<p><strong>热替换的关键需求在于服务不能中断，修改必须立即表现正在运行的系统之中</strong></p>
<p>基本上大部分脚本语言都是天生支持热替换的，比如：PHP，只要替换了 PHP 源文件，这种改动就会立即生效，而无需重启 Web 服务器</p>
<p>但对 Java 来说，热替换并非天生就支持，如果一个类已经加载到系统中，通过修改类文件，并无法让系统再来加载并重定义这个类</p>
<p>因此，在 Java 中实现这一功能的一个可行的方法就是灵活运用 ClassLoader</p>
<p>注意：由不同 ClassLoader 加载的同名类属于不同的类型，不能相互转换和兼容。即两个不同的ClassLoader加载同一个类，在虚拟机内部，会认为这 2 个类是完全不同的</p>
<p>根据这个特点，可以用来模拟热替换的实现，基本思路如下图所示：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%83%AD%E6%9B%BF%E6%8D%A2.png" alt="热替换"></p>
</li>
</ol>
<h4 id="6-沙箱安全机制"><a href="#6-沙箱安全机制" class="headerlink" title="6.沙箱安全机制"></a>6.沙箱安全机制</h4><blockquote>
<p>沙箱安全机制</p>
<ul>
<li>保证程序安全</li>
<li>保护 Java 原生的 JDK 代码</li>
</ul>
<p><strong>Java 安全模型的核心就是 Java 沙箱（sandbox）</strong></p>
<p><strong>什么是沙箱？沙箱是一个限制程序运行的环境</strong></p>
<p>沙箱机制就是将 <strong>Java 代码限定在虚拟机（JVM）特定的运行范围中，并且严格限制代码对本地系统资源访问</strong></p>
<p>通过这样的措施来保证对代码的有限隔离，防止对本地系统造成破坏</p>
<p>沙箱主要限制系统资源访问，那系统资源包括什么？CPU、内存、文件系统、网络</p>
<p>不同级别的沙箱对这些资源访问的限制也可以不一样</p>
<p>所有的 Java 程序运行都可以指定沙箱，可以定制安全策略</p>
</blockquote>
<ol>
<li><p>JDK1.0 时期</p>
<blockquote>
<p>在Java中将执行程序分成本地代码和远程代码两种，本地代码默认视为可信任的，而远程代码则被看作是不受信的</p>
<p>对于授信的本地代码，可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中，安全依赖于<strong>沙箱（Sandbox）机制</strong></p>
<p>如下图所示JDK1.0安全模型</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%B2%99%E7%AE%B11.0.png" alt="沙箱1.0"></p>
</li>
<li><p>JDK1.1 时期</p>
<blockquote>
<p>JDK1.0 中如此严格的安全机制也给程序的功能扩展带来障碍，比如当用户希望远程代码访问本地系统的文件时候，就无法实现</p>
<p>因此在后续的 Java1.1版本中，针对安全机制做了改进，增加了<strong>安全策略</strong></p>
<p>允许用户指定代码对本地资源的访问权限</p>
<p>如下图所示 JDK1.1安全模型</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%B2%99%E7%AE%B11.1.png" alt="沙箱1.1"></p>
</li>
<li><p>JDK1.2 时期</p>
<blockquote>
<p>在Java1.2版本中，再次改进了安全机制，增加了<strong>代码签名</strong></p>
<p>不论本地代码或是远程代码，都会按照用户的安全策略设定，由类加载器加载到虚拟机中权限不同的运行空间，来实现差异化的代码执行权限控制</p>
<p>如下图所示 JDK1.2 安全模型：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%B2%99%E7%AE%B11.2.png" alt="沙箱1.2"></p>
</li>
<li><p>JDK1.6 时期</p>
<blockquote>
<p>当前最新的安全机制实现，则引入了<strong>域（Domain）</strong>的概念</p>
<p>虚拟机会把所有代码加载到不同的系统域和应用域</p>
<p><strong>系统域部分专门负责与关键资源进行交互</strong>，而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问</p>
<p>虚拟机中不同的受保护域（Protected Domain），对应不一样的权限（Permission）</p>
<p>存在于不同域中的类文件就具有了当前域的全部权限</p>
<p>如下图所示，最新的安全模型（JDK1.6）</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%B2%99%E7%AE%B11.6.png" alt="沙箱1.6"></p>
</li>
</ol>
<h4 id="7-自定义类的加载器"><a href="#7-自定义类的加载器" class="headerlink" title="7.自定义类的加载器"></a>7.自定义类的加载器</h4><ol>
<li><p>为什么要自定义类加载器</p>
<ul>
<li><p><strong>隔离加载类</strong></p>
<blockquote>
<p>在某些框架内进行中间件与应用的模块隔离，把类加载到不同的环境</p>
<p>比如：阿里内某容器框架通过自定义类加载器确保应用中依赖的 jar 包不会影响到中间件运行时使用的 jar 包</p>
<p>再比如：Tomcat 这类 Web 应用服务器，内部自定义了好几种类加载器，用于隔离同一个 Web 应用服务器上的不同应用程序</p>
<p>（类的仲裁 –&gt; 类冲突）</p>
</blockquote>
</li>
<li><p><strong>修改类加载的方式</strong></p>
<blockquote>
<p>类的加载模型并非强制，除 Bootstrap 外，其他的加载并非一定要引入，或者根据实际情况在某个时间点进行按需进行动态加载</p>
</blockquote>
</li>
<li><p><strong>扩展加载源</strong></p>
<blockquote>
<p>比如从数据库、网络、甚至是电视机机顶盒进行加载</p>
</blockquote>
</li>
<li><p><strong>防止源码泄漏</strong></p>
<blockquote>
<p>Java 代码容易被编译和篡改，可以进行编译加密</p>
<p>那么类加载也需要自定义，还原加密的字节码</p>
</blockquote>
</li>
</ul>
</li>
<li><p>常见应用场景</p>
<ul>
<li><p>实现类似进程内隔离，类加载器实际上用作不同的命名空间，以提供类似容器、模块化的效果</p>
<blockquote>
<p>例如，两个模块依赖于某个类库的不同版本，如果分别被不同的容器加载，就可以互不干扰</p>
<p>这个方面的集大成者是 JavaEE 和 OSGi、JPMS 等框架</p>
</blockquote>
</li>
<li><p>应用需要从不同的数据源获取类定义信息</p>
<blockquote>
<p>例如网络数据源，而不是本地文件系统</p>
<p>或者是需要自己操纵字节码，动态修改或者生成类型</p>
</blockquote>
</li>
</ul>
<blockquote>
<p><strong>注意</strong></p>
<p>在一般情况下，使用不同的类加载器去加载不同的功能模块，会提高应用程序的安全性</p>
<p>但是如果涉及 Java 类型转换，则加载器反而容易产生不美好的事情</p>
<p><strong>在做 Java 类型转换时，只有两个类型都是由同一个加载器所加载，才能进行类型转换，否则转换时会发生异常</strong></p>
</blockquote>
</li>
<li><p>实现方式</p>
<blockquote>
<p>Java 提供了抽象类 <code>java.lang.ClassLoader</code>，所有用户自定义的类加载器都应该继承 <code>ClassLoader</code> 类</p>
<p>在自定义 <code>ClassLoader</code> 的子类时候，我们常见的会有两种做法</p>
<ul>
<li>重写 loadClass() 方法</li>
<li>重写 findclass() 方法</li>
</ul>
</blockquote>
<ul>
<li><p>两种实现方式对比</p>
<ul>
<li><p>这两种方法本质上差不多，毕竟 loadClass() 也会调用 findClass()，但是从逻辑上讲我们最好不要直接修改 loadClass() 的内部逻辑</p>
<blockquote>
<p>建议的做法是只在 findClass() 里重写自定义类的加载方法，根据参数指定类的名字，返回对应的Class对象的引用</p>
</blockquote>
</li>
<li><p>loadclass() 这个方法是实现双亲委派模型逻辑的地方，擅自修改这个方法会导致模型被破坏，容易造成问题</p>
<blockquote>
<p>因此最好是在双亲委派模型框架内进行小范围的改动，不破坏原有的稳定结构</p>
<p>同时，也避免了自己重写 loadClass() 方法的过程中必须写双亲委托的重复代码，从代码的复用性来看，不直接修改这个方法始终是比较好的选择</p>
</blockquote>
</li>
<li><p>当编写好自定义类加载器后，便可以在程序中调用 loadClass() 方法来实现类加载操作</p>
</li>
</ul>
</li>
<li><p>说明</p>
<ul>
<li>其父类加载器是系统类加载器</li>
<li>JVM 中的所有类加载都会使用 <code>java.lang.ClassLoader.loadClass(String)</code> 接口（自定义类加载器并重写 <code>java.lang.ClassLoader.loadClass(String)</code> 接口的除外），连JDK的核心类库也不能例外</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="8-Java9-新特性"><a href="#8-Java9-新特性" class="headerlink" title="8.Java9 新特性"></a>8.Java9 新特性</h4><blockquote>
<p>为了保证兼容性，JDK9 没有从根本上改变三层类加载器架构和双亲委派模型，但为了模块化系统的顺利运行，仍然发生了一些值得被注意的变动</p>
</blockquote>
<ul>
<li><p>扩展机制被移除</p>
<blockquote>
<p>扩展类加载器由于向后兼容性的原因被保留，不过被重命名为平台类加载器（platform class loader）</p>
<p>可以通过 classLoader 的新方法 getPlatformClassLoader() 来获取</p>
<p>JDK9时基于模块化进行构建（原来的 rt.jar 和 tools.jar 被拆分成数十个 JMOD 文件），其中的 Java 类库就已天然地满足了可扩展的需求，那自然无须再保留 <JAVA_HOME>\lib\ext 目录，此前使用这个目录或者 java.ext.dirs 系统变量来扩展 JDK 功能的机制已经没有继续存在的价值了</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java9%E6%96%B0%E7%89%B9%E6%80%A72.png" alt="Java9新特性2"></p>
</li>
<li><p>平台类加载器和应用程序类加载器都不再继承自 <code>java.net.URLClassLoader</code></p>
<blockquote>
<p>现在启动类加载器、平台类加载器、应用程序类加载器全都继承于<code>jdk.internal.loader.BuiltinClassLoader</code></p>
<p>如果有程序直接依赖了这种继承关系，或者依赖了 URLClassLoader 类的特定方法，那代码很可能会在 JDK9 及更高版本的 JDK 中崩溃</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java9%E6%96%B0%E7%89%B9%E6%80%A71.png" alt="Java9新特性1"></p>
</li>
<li><p>在Java9中，类加载器有了名称</p>
<blockquote>
<p>该名称在构造方法中指定，可以通过getName()方法来获取</p>
<p>平台类加载器的名称是 platform，应用类加载器的名称是 app</p>
<p>类加载器的名称在调试与类加载器相关的问题时会非常有用</p>
</blockquote>
</li>
<li><p>启动类加载器现在是在 jvm 内部和 java 类库共同协作实现的类加载器（以前是C++实现）</p>
<blockquote>
<p>但为了与之前代码兼容，在获取启动类加载器的场景中仍然会返回 null，而不会得到 BootClassLoader 实例。</p>
</blockquote>
</li>
<li><p>类加载的委派关系也发生了变动</p>
<blockquote>
<p>当平台及应用程序类加载器收到类加载请求，在委派给父加载器加载前，要先判断该类是否能够归属到某一个系统模块中，如果可以找到这样的归属关系，就要优先委派给负责那个模块的加载器完成加载</p>
</blockquote>
</li>
</ul>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java9%E6%96%B0%E7%89%B9%E6%80%A73.png" alt="Java9新特性3"></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java9%E6%96%B0%E7%89%B9%E6%80%A74.png" alt="Java9新特性4"></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java9%E6%96%B0%E7%89%B9%E6%80%A75.png" alt="Java9新特性5"></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ClassLoaderTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(ClassLoaderTest.class.getClassLoader());</span><br><span class="line">        System.out.println(ClassLoaderTest.class.getClassLoader().getParent());</span><br><span class="line">		System.out.println(ClassLoaderTest.class.getClassLoader().getParent().getParent());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取系统类加载器</span></span><br><span class="line">        System.out.println(ClassLoader.getSystemClassLoader());</span><br><span class="line">        <span class="comment">//获取平台类加载器</span></span><br><span class="line">        System.out.println(ClassLoader.getPlatformClassLoader());</span><br><span class="line">        <span class="comment">//获取类的加载器的名称</span></span><br><span class="line">        System.out.println(ClassLoaderTest.class.getClassLoader().getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id=""><a href="#" class="headerlink" title=""></a></h4></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/10/08/JVM%20%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87/">https://gitee.com/zcmmmm/zcmmmm/2022/10/08/JVM%20%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/JVM/">JVM</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/10/11/JVM%20%E4%B8%8B%E7%AF%87-%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98%E7%AF%87/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">JVM 下篇-性能监控与调优篇</div></div></a></div><div class="next-post pull-right"><a href="/2022/10/07/JVM%20%E7%95%AA%E5%A4%96-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%8B%AC%E7%AB%8B%E7%AF%87/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">JVM 番外-垃圾回收独立篇</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/10/11/JVM%20%E4%B8%8B%E7%AF%87-%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98%E7%AF%87/" title="JVM 下篇-性能监控与调优篇"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-11</div><div class="title">JVM 下篇-性能监控与调优篇</div></div></a></div><div><a href="/2022/09/28/JVM%20%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/" title="JVM 上篇-内存与垃圾回收"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-09-28</div><div class="title">JVM 上篇-内存与垃圾回收</div></div></a></div><div><a href="/2022/10/07/JVM%20%E7%95%AA%E5%A4%96-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%8B%AC%E7%AB%8B%E7%AF%87/" title="JVM 番外-垃圾回收独立篇"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-07</div><div class="title">JVM 番外-垃圾回收独立篇</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#JVM-%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87"><span class="toc-text">JVM 中篇-字节码与类的加载篇</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-Class-%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84"><span class="toc-text">1.Class 文件结构</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-Class-%E5%AD%97%E8%8A%82%E7%A0%81%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84"><span class="toc-text">1.Class 字节码文件结构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-Class-%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="toc-text">2.Class 文件数据类型</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E9%AD%94%E6%95%B0%EF%BC%88Magic-Number%EF%BC%89"><span class="toc-text">3.魔数（Magic Number）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E6%96%87%E4%BB%B6%E7%89%88%E6%9C%AC%E5%8F%B7"><span class="toc-text">4.文件版本号</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E5%B8%B8%E9%87%8F%E6%B1%A0%E9%9B%86%E5%90%88"><span class="toc-text">5.常量池集合</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E8%AE%BF%E9%97%AE%E6%A0%87%E5%BF%97%EF%BC%88access-flag%E3%80%81%E8%AE%BF%E9%97%AE%E6%A0%87%E8%AF%86%E3%80%81%E8%AE%BF%E9%97%AE%E6%A0%87%E8%AE%B0%EF%BC%89"><span class="toc-text">6.访问标志（access_flag、访问标识、访问标记）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E7%B1%BB%E7%B4%A2%E5%BC%95%E3%80%81%E7%88%B6%E7%B1%BB%E7%B4%A2%E5%BC%95%E3%80%81%E6%8E%A5%E5%8F%A3%E7%B4%A2%E5%BC%95"><span class="toc-text">7.类索引、父类索引、接口索引</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E5%AD%97%E6%AE%B5%E8%A1%A8%E9%9B%86%E5%90%88"><span class="toc-text">8.字段表集合</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#9-%E6%96%B9%E6%B3%95%E8%A1%A8%E9%9B%86%E5%90%88"><span class="toc-text">9.方法表集合</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#10-%E5%B1%9E%E6%80%A7%E8%A1%A8%E9%9B%86%E5%90%88"><span class="toc-text">10.属性表集合</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E5%AD%97%E8%8A%82%E7%A0%81%E6%8C%87%E4%BB%A4%E9%9B%86%E4%B8%8E%E8%A7%A3%E6%9E%90%E4%B8%BE%E4%BE%8B"><span class="toc-text">2.字节码指令集与解析举例</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E6%A6%82%E8%BF%B0"><span class="toc-text">1.概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%AD%98%E5%82%A8%E6%8C%87%E4%BB%A4"><span class="toc-text">2.加载与存储指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E7%AE%97%E6%9C%AF%E6%8C%87%E4%BB%A4"><span class="toc-text">3.算术指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E6%8C%87%E4%BB%A4"><span class="toc-text">4.类型转换指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E8%AE%BF%E9%97%AE%E6%8C%87%E4%BB%A4"><span class="toc-text">5.对象的创建与访问指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E4%B8%8E%E8%BF%94%E5%9B%9E%E6%8C%87%E4%BB%A4"><span class="toc-text">6.方法调用与返回指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88%E7%AE%A1%E7%90%86%E6%8C%87%E4%BB%A4"><span class="toc-text">7.操作数栈管理指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E6%8E%A7%E5%88%B6%E8%BD%AC%E7%A7%BB%E6%8C%87%E4%BB%A4"><span class="toc-text">8.控制转移指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#9-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86%E6%8C%87%E4%BB%A4"><span class="toc-text">9.异常处理指令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#10-%E5%90%8C%E6%AD%A5%E6%8E%A7%E5%88%B6%E6%8C%87%E4%BB%A4"><span class="toc-text">10.同步控制指令</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3"><span class="toc-text">3.类的加载过程详解</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E6%A6%82%E8%BF%B0-1"><span class="toc-text">1.概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E8%BF%87%E7%A8%8B%E4%B8%80%EF%BC%9A%E5%8A%A0%E8%BD%BD%EF%BC%88Loading%EF%BC%89%E9%98%B6%E6%AE%B5"><span class="toc-text">2.过程一：加载（Loading）阶段</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E8%BF%87%E7%A8%8B%E4%BA%8C%EF%BC%9A%E9%93%BE%E6%8E%A5%EF%BC%88Linking%EF%BC%89%E9%98%B6%E6%AE%B5"><span class="toc-text">3.过程二：链接（Linking）阶段</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E8%BF%87%E7%A8%8B%E4%B8%89%EF%BC%9A%E5%88%9D%E5%A7%8B%E5%8C%96%EF%BC%88Initilazation%EF%BC%89%E9%98%B6%E6%AE%B5"><span class="toc-text">4.过程三：初始化（Initilazation）阶段</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E8%BF%87%E7%A8%8B%E5%9B%9B%EF%BC%9A%E7%B1%BB%E7%9A%84%E4%BD%BF%E7%94%A8%EF%BC%88Using%EF%BC%89"><span class="toc-text">5.过程四：类的使用（Using）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E8%BF%87%E7%A8%8B%E4%BA%94%EF%BC%9A%E7%B1%BB%E7%9A%84%E5%8D%B8%E8%BD%BD%EF%BC%88Unloading%EF%BC%89"><span class="toc-text">6.过程五：类的卸载（Unloading）</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E5%86%8D%E8%B0%88%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">4.再谈类的加载器</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E6%A6%82%E8%BF%B0-2"><span class="toc-text">1.概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%88%86%E7%B1%BB"><span class="toc-text">2.类的加载器分类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E6%B5%8B%E8%AF%95%E4%B8%8D%E5%90%8C%E7%9A%84%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">3.测试不同的类的加载器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-ClassLoader-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90"><span class="toc-text">4.ClassLoader 源码解析</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B"><span class="toc-text">5.双亲委派模型</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E6%B2%99%E7%AE%B1%E5%AE%89%E5%85%A8%E6%9C%BA%E5%88%B6"><span class="toc-text">6.沙箱安全机制</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E5%99%A8"><span class="toc-text">7.自定义类的加载器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-Java9-%E6%96%B0%E7%89%B9%E6%80%A7"><span class="toc-text">8.Java9 新特性</span></a></li><li class="toc-item toc-level-4"><a class="toc-link"><span class="toc-text"></span></a></li></ol></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>